
デッキ構築ゲームの面白さは、「敵が次に何をしてくるか分かっていて、それを見て攻めるか守るかを決める」ところにあります。今回は、スライムちゃんと敵のHPのゲージを作り、敵のデータをScriptableObjectにして、敵を2体並べ、次の行動の予告を頭の上に出してみたいと思います!
- シリーズの目次:デッキ構築カードゲームの作り方
- 前回:#7 エネルギーとカードの効果を作ろう
今回作るもの
- スライムちゃんと敵のHPのゲージ(防御があるときは盾と数字)
- 敵1種類のデータ(名前・絵・最大HP・行動の並び)
- 敵のプレハブ:HPのゲージと、次の行動の予告
- 敵のデータのリストから、敵を横に並べて作る
- 2体目の敵「びっくり箱」
| スクリプト | 役割 | 付ける場所 |
|---|---|---|
| HpGauge | HPをゲージと数字で、防御を盾で表示する | PlayerArea・Enemy |
| EnemyData | 敵1種類のデータ | (付けない。アセットを作る) |
| EnemyView | 敵の絵・名前と、次の行動の予告を表示する | Enemy(プレハブ) |
| EnemyParty | 敵を横に並べて作る | EnemyArea |
| Fighter(直す) | データから名前と最大HPを入れられるようにする | - |
| EnemyTarget(直す) | 名前をFighterから取る | - |
予告が出るところまで作ります。敵が予告どおりに行動するのは、ターンの流れを作る#9です。
前回の続きから
前回の最後の状態から始めます。びっくり箱の絵と予告のアイコン、ゲージ用の白い四角は、素材のzipを入れ直すと入ります。
- DeckBuildingAssets.zip
DeckBuilding/Sprites/Enemies/enemy_jackbox.png(びっくり箱)DeckBuilding/Sprites/Icons/icon_attack.png・icon_block.png・icon_charge.png(剣・盾・ためる)DeckBuilding/Sprites/UI/ui_white.png(まっさらな白い四角。HPのゲージに使う)- 自分で入れた場合は、Sprite Mode:Singleにして Apply
Fighterを直そう
#7のFighterは、名前と最大HPをInspectorで決めて、HPを文字(StatusText)で出していました。今回からHPはゲージで出すので、文字の表示は消します。また、敵はデータから作るので、名前と最大HPをスクリプトから入れられるようにします。直したところには★#8と書いてあります。
using UnityEngine;
// HP と防御を持って、ダメージ・防御・回復を受ける(スライムちゃんと敵の共通部分)
public class Fighter : MonoBehaviour
{
[SerializeField] private string fighterName = "スライムちゃん";
[SerializeField] private int maxHp = 60;
private int hp;
private int block; // 防御:ダメージを先に受けとめてくれる
public string FighterName => fighterName;
public int Hp => hp;
public int MaxHp => maxHp; // ★#8 ゲージで使う
public int Block => block; // ★#8 ゲージで使う
private void Awake()
{
hp = maxHp;
}
// ★#8 名前と最大HPを入れて、HPを満タンにする(敵をデータから作るときに使う)
public void Setup(string newName, int newMaxHp)
{
fighterName = newName;
maxHp = newMaxHp;
hp = maxHp;
block = 0;
}
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}");
}
public void AddBlock(int amount)
{
block += amount;
Debug.Log($"{fighterName}の防御 +{amount} 防御 {block}");
}
public void Heal(int amount)
{
hp = Mathf.Min(maxHp, hp + amount);
Debug.Log($"{fighterName}のHPが{amount}回復 HP {hp}/{maxHp}");
}
}
- statusText・UpdateTextを消した
- HPの表示は、このあと作るHpGaugeにまかせる。Fighterは「HPと防御の数字を持つ」だけにして、見せ方は別のスクリプトに分けておくと、ゲージを演出付きに変えたいとき(#11)もFighterはさわらずに済みます
- MaxHp・Block
- ゲージと防御の盾を出すために、ほかのスクリプトから読めるようにした
- Setup
- 名前と最大HPを入れて、HPを満タン・防御を0にする
Fighterから文字の表示を消したので、#7で作ったPlayerAreaのStatusTextは消しておきましょう。
HPのゲージを作ろう
ゲージの部品を作る
まずは敵のゲージから作ります。#6で置いたEnemyの子に、次のものを作ります(#7でEnemyに作ったStatusTextは消します)。
- HpBar(UI (Canvas)>Image)
- アンカー:bottom-center、Pos Y:-68、Width:200、Height:34
- Color:#3C2F2F(こげ茶。ゲージの背景)
- HpBarの子にHpFill(UI (Canvas)>Image)
- アンカー:stretch-stretch、Left・Top・Right・Bottom:3
- Source Image:ui_white(素材のzipの
DeckBuilding/Sprites/UIにある白い四角。空だとImage Typeが選べません)、Image Type:Filled、Fill Method:Horizontal、Fill Origin:Left - Color:#E8584C(赤)
- HpBarの子にHpText(Text – TextMeshPro)
- アンカー:stretch-stretch、Left・Top・Right・Bottom:0
- テキスト:「28/28」、Font Size:26、Font Style:B、Alignment:中央、Color:白
- BlockBadge(UI (Canvas)>Image)
- アンカー:bottom-center、Pos X:-126、Pos Y:-68、Width・Height:40
- Source Image:icon_block、Raycast Targetのチェックを外す
- BlockBadgeの子にBlockText(Text – TextMeshPro)
- アンカー:stretch-stretch、テキスト:「0」、Font Size:22、Font Style:B、Alignment:中央、Color:白

ゲージには、角の丸いUISpriteではなく、白い四角(ui_white)を使います。Filledは画像を切り分けずにそのまま伸ばすので、角の丸い画像だとゲージの両端が丸くつぶれて、見苦しくなります。色はImageのColorで付けるので、画像は白い四角が1枚あれば、どの色のゲージにも使えます。
HpGaugeを作る
- DeckBuilding/Scriptsに「HpGauge」スクリプトを作る
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// Fighter の HP をゲージと数字で、防御を盾と数字で表示する(スライムちゃんと敵の共通部分)
[RequireComponent(typeof(Fighter))]
public class HpGauge : MonoBehaviour
{
[SerializeField] private Image hpFill;
[SerializeField] private TextMeshProUGUI hpText;
[SerializeField] private GameObject blockBadge;
[SerializeField] private TextMeshProUGUI blockText;
private Fighter fighter;
private void Awake()
{
fighter = GetComponent<Fighter>();
}
private void Update()
{
// HP のゲージと数字
hpFill.fillAmount = (float)fighter.Hp / fighter.MaxHp;
hpText.text = $"{fighter.Hp}/{fighter.MaxHp}";
// 防御は、あるときだけ盾を出す
blockBadge.SetActive(fighter.Block > 0);
blockText.text = fighter.Block.ToString();
}
}
- (float)fighter.Hp / fighter.MaxHp
- HPの割合(0〜1)。intどうしの割り算は小数が切り捨てられて0になってしまうので、片方を(float)にしてから割る
- blockBadge.SetActive
- 防御が0のときは盾ごと隠す。防御があるときだけ出るので、「今、防御がある」ことが見ただけで分かる
- [RequireComponent(typeof(Fighter))]
- Fighterと同じオブジェクトに付ける決まりにしておく。Fighterが無いところに付けると、自動でFighterも付く
EnemyにHpGaugeを付けて、Hp Fill:HpFill、Hp Text:HpText、Block Badge:BlockBadge、Block Text:BlockTextを入れます。
スライムちゃんにも付ける
スライムちゃんのゲージも、同じ部品で作れます。敵のゲージを複製して持ってきましょう。
- HierarchyでEnemyの子のHpBarとBlockBadgeを選んで、Ctrl+D(MacはCmd+D)で複製
- 複製した2つを、PlayerAreaの上にドラッグ&ドロップして、PlayerAreaの子にする
- 名前の「(1)」を消して、位置を変える
- HpBar:Pos Y:20、Width:240、Height:38
- BlockBadge:Pos X:-150、Pos Y:20
- HpText のテキスト:「60/60」、Font Size:28
- PlayerAreaにHpGaugeを付けて、PlayerAreaの子のHpFill・HpText・BlockBadge・BlockTextを入れる

スライムわたしのゲージも、敵と同じ見た目になりました!



HpGaugeはFighterを読むだけだから、どっちに付けても同じように動くんだ。#9で敵に攻撃されると、このゲージが減っていくよ
敵のデータを作ろう
カードと同じように、敵もデータをScriptableObjectにしておきます(12-8 ScriptableObjectでデータを作ろう)。
- DeckBuilding/Scriptsに「EnemyData」スクリプトを作る
using System.Collections.Generic;
using UnityEngine;
// 敵1種類のデータ。Project ウインドの Create>DeckBuilding>Enemy Data でアセットを作れる
[CreateAssetMenu(fileName = "NewEnemy", menuName = "DeckBuilding/Enemy Data")]
public class EnemyData : ScriptableObject
{
// 敵の1回分の行動(攻撃と防御。どちらも0なら「ためる」)
[System.Serializable]
public class EnemyAction
{
[Min(0)] public int attack;
[Min(0)] public int block;
}
public string enemyName; // 画面に出す名前
public Sprite sprite; // 絵
[Min(1)] public int maxHp = 20; // 最大HP
public List<EnemyAction> actions = new List<EnemyAction>(); // 行動の並び(上から順にくり返す)
}
- EnemyAction
- 敵の1回分の行動。「攻撃いくつ」と「防御いくつ」の組にしておき、どちらも0なら「ためる」(何もしない)として扱う
- actions
- 行動を上から順に使って、最後まで行ったら最初に戻る。「攻撃・攻撃・大きい攻撃」のような、敵ごとのくせを表せる
アセットを作ります。
- DeckBuilding>Dataの中に「Enemies」フォルダを作り、右クリック>Create>DeckBuilding>Enemy Data で2つ作る
| アセットの名前 | Enemy Name | Sprite | Max Hp | Actions(上から) |
|---|---|---|---|---|
| TinRobot | ブリキロボ | enemy_tinrobot | 28 | 攻撃6 → 攻撃6 → 攻撃9 |
| JackBox | びっくり箱 | enemy_jackbox | 36 | ためる(0・0) → 攻撃16 |





びっくり箱は、1ターン目に何もしないんですか?



そう、ためてから大きい一撃!予告で「…」が見えたら、次のターンに備えて防御カードを取っておく、みたいに考えられるのがこのジャンルの楽しいところだよ
行動の予告を作ろう
Enemyの子に、頭の上の予告を作ります。
- Intent(Create Empty)
- アンカー:top-center、Pos Y:50、Width:140、Height:60
- Intentの子にIntentIcon(UI (Canvas)>Image)
- アンカー:middle-center、Pos X:-28、Width・Height:56、Source Image:icon_attack、Raycast Targetのチェックを外す
- Intentの子にIntentText(Text – TextMeshPro)
- アンカー:middle-center、Pos X:34、Width:70、Height:60
- テキスト:「6」、Font Size:44、Font Style:B、Alignment:左寄せ・上下中央、Color:#C0392B(こい赤)
これで、敵の部品がそろいました。
- Enemy(敵の絵)
- TargetFrame(ねらいの枠。#6)
- NameText(名前。#6)
- HpBar(ゲージの背景)
- HpFill(ゲージの中身)
- HpText(「28/28」)
- BlockBadge(防御の盾)
- BlockText
- Intent(頭の上の予告)
- IntentIcon(剣・盾・…)
- IntentText(数字)


EnemyViewを作る
敵の絵と名前を入れて、次の行動の予告を出すスクリプトです。
- DeckBuilding/Scriptsに「EnemyView」スクリプトを作る
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// 敵の見た目:絵・名前と、次の行動の予告(HP のゲージは HpGauge)
[RequireComponent(typeof(Fighter))]
public class EnemyView : MonoBehaviour
{
[SerializeField] private Image bodyImage;
[SerializeField] private TextMeshProUGUI nameText;
[Header("行動の予告")]
[SerializeField] private RectTransform intent;
[SerializeField] private Image intentIcon;
[SerializeField] private TextMeshProUGUI intentText;
[SerializeField] private Sprite attackIcon;
[SerializeField] private Sprite blockIcon;
[SerializeField] private Sprite chargeIcon;
private EnemyData data;
private Fighter fighter;
private int actionIndex; // 次に使う行動の番号
public EnemyData Data => data;
public Fighter Fighter => fighter;
public EnemyData.EnemyAction NextAction => data.actions[actionIndex];
private void Awake()
{
fighter = GetComponent<Fighter>();
}
// データを受け取って、見た目と HP を入れる
public void Setup(EnemyData enemyData)
{
data = enemyData;
bodyImage.sprite = data.sprite;
nameText.text = data.enemyName;
fighter.Setup(data.enemyName, data.maxHp);
actionIndex = 0;
ShowIntent();
}
// 次の行動の予告を出す
private void ShowIntent()
{
EnemyData.EnemyAction action = NextAction;
if (action.attack > 0)
{
intentIcon.sprite = attackIcon;
intentText.text = action.attack.ToString();
}
else if (action.block > 0)
{
intentIcon.sprite = blockIcon;
intentText.text = action.block.ToString();
}
else
{
intentIcon.sprite = chargeIcon; // ためる
intentText.text = "";
}
intent.localScale = Vector3.one * 1.6f; // ポンと大きく出して、Update で元の大きさに戻す
}
private void Update()
{
// 予告の大きさを、1へなめらかに戻す
intent.localScale = Vector3.Lerp(intent.localScale, Vector3.one, Mathf.Min(1f, 12f * Time.deltaTime));
}
}
- Setup
- データの絵と名前を表示して、FighterにHPを入れる。最後に予告を出す(ゲージはHpGaugeが勝手に合わせてくれる)
- ShowIntent
- 次の行動が攻撃なら剣と数字、防御なら盾と数字、どちらも0なら「…」(ためる)
intent.localScaleを1.6倍にしてから、Updateで1へ戻すと、予告がポンと飛び出すように見える
- NextAction
- 次に使う行動。#9で、敵のターンにこの行動をして、次の行動へ進めます
EnemyDataのActionsが空のままだと、NextActionでArgumentOutOfRangeExceptionのエラーになります。敵のデータには、行動を1つ以上入れておきましょう。
EnemyTargetを直す
#6のEnemyTargetは、名前をInspectorで入れていました。名前はFighterが持っているので、そこから取るように直します。
using UnityEngine;
using UnityEngine.EventSystems;
// 攻撃のカードを持って上に来たら、ねらっている印の枠を出す
public class EnemyTarget : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private GameObject targetFrame; // ねらっている印の枠
public string EnemyName => GetComponent<Fighter>().FighterName; // ★#8 名前は Fighter から取る
private void Start()
{
targetFrame.SetActive(false);
}
public void OnPointerEnter(PointerEventData eventData)
{
// 攻撃のカードをドラッグしているときだけ、枠を出す
if (eventData.pointerDrag == null)
{
return;
}
CardDrag card = eventData.pointerDrag.GetComponent<CardDrag>();
if (card != null && card.IsAttack)
{
targetFrame.SetActive(true);
}
}
public void OnPointerExit(PointerEventData eventData)
{
targetFrame.SetActive(false);
}
}
敵をプレハブにして並べよう
プレハブにする
- EnemyにEnemyViewを付けて、部品をドラッグ&ドロップ
- Body Image:Enemy(自分自身)、Name Text:NameText
- Intent:Intent、Intent Icon:IntentIcon、Intent Text:IntentText
- Attack Icon・Block Icon・Charge Icon:icon_attack・icon_block・icon_charge
- Enemyを、ProjectウインドのPrefabsにドラッグ&ドロップしてプレハブにする
- プレハブにできたら、Hierarchyの Enemy は消す(次のEnemyPartyがスクリプトから作ります)


敵を並べて作る
最後に、敵のデータのリストから、敵を横に並べて作るEnemyPartyです。EnemyAreaに付けます。
using System.Collections.Generic;
using UnityEngine;
// 敵のデータのリストから、敵を横に並べて作る
public class EnemyParty : MonoBehaviour
{
[SerializeField] private EnemyView enemyPrefab;
[SerializeField] private List<EnemyData> enemies = new List<EnemyData>();
[SerializeField] private float spacing = 420f; // となりの敵との間隔
private List<EnemyView> views = new List<EnemyView>();
public List<EnemyView> Views => views;
private void Start()
{
// 真ん中を0にして、左右に同じだけ広げる
float left = -(enemies.Count - 1) * spacing / 2f;
for (int i = 0; i < enemies.Count; i++)
{
EnemyView view = Instantiate(enemyPrefab, transform);
((RectTransform)view.transform).anchoredPosition = new Vector2(left + i * spacing, 20f);
view.Setup(enemies[i]);
views.Add(view);
}
}
}
- 並べ方は、#4で手札を横一列に並べたときと同じ考え方です(真ん中を0にして、左右に広げる)
- Views
- 作った敵のリスト。#9で、敵のターンに1体ずつ行動させるときに使います
EnemyAreaに付けて、設定します。
- HierarchyのEnemyAreaにEnemyPartyを付ける
- Enemy Prefab:Prefabs>Enemy
- Enemies:「+」で2つにして、TinRobot・JackBox
- Spacing:420


戦う敵を変えたいときは、Enemiesのリストを入れかえるだけ。1体にしたり3体にしたりも、ここだけでできます。#14の3連戦では、このリストを戦いごとに入れかえます。
動かして確認!
再生してみましょう。




#8の動作確認
- 再生すると、スライムちゃんの下にHPのゲージ「60/60」が出る
- ブリキロボとびっくり箱が横に並び、それぞれにゲージが出る
- ブリキロボの頭の上に剣と「6」、びっくり箱の頭の上に「…」の予告が、ポンと出る
- 攻撃のカードを敵に使うと、その敵のゲージが縮んで、数字が減る
- 下じきガードを使うと、スライムちゃんのゲージの横に盾と「5」が出る
あとがき
今回は、HPのゲージと、敵のデータ、行動の予告を作りました。「次に何が来るか」が見えるだけで、手札を見ながら考える時間がぐっと楽しくなりますね。次回は、ターン終了ボタンで敵のターンにして、敵が予告どおりに行動する「ターンの流れ」を作りたいと思います!
コメント