ヴァンサバ風ゲームの作り方#7|レベルアップで3択の強化を選ばせよう

レベルアップでゲームが止まって3枚のカードが出て、選ぶと再開する様子

前回でレベルが上がるようになりましたが、上がっても強くはなりませんでした。ヴァンサバ風のゲームで一番おいしいのは、レベルアップのたびに出てくる3択の強化です。何を選ぶかで戦い方が変わっていくのが、このジャンルの面白さそのものだと思います。今回はヴァンサバ風サバイバーゲームのシリーズの7回目として、レベルアップでゲームを止めて、3枚のカードから1つ選ぶ仕組みを作ってみたいと思います!

目次

今回作るもの

  • レベルアップでゲームが止まり、3枚のカードが出る
  • カードを押すと強化されて、ゲームが再開する
  • 強化は4種類。同じカードは1回に2枚出ない
  • 3つ目の武器ストレーナー(足元に粉をふるう範囲攻撃)が手に入る

新しく作るスクリプトが10個、直すスクリプトが4つあります。少し多いですが、1つ1つは短いです。

スクリプト役割付ける場所
Weapon(新しく作る)武器に共通する部分。データのコピーを持つ(親クラス)
UpgradeData(新しく作る)強化1つ分の親。名前・説明・アイコン(親クラス)
PowerUpgrade / SpeedUpgrade / SpoonUpgrade / StrainerUpgrade強化の効き方。1種類につき1つデータのアセット
StrainerData(新しく作る)ストレーナーの性能データのアセット
StrainerArea(新しく作る)足元の丸い範囲にダメージを与えるPlayer
UpgradeCard(新しく作る)カード1枚の見た目と、押されたときの合図Card(3枚)
UpgradeView(新しく作る)3枚を選んで出す。止める・再開するCanvas
SpoonThrower / WhipperOrbit(直す)Weaponを親にする。スプーンは本数を増やせるようにPlayer
SpoonData(直す)投げる本数と、広がる角度を足すデータのアセット
PlayerMove(直す)外から移動速度を上げられるようにPlayer

前回の続きから

  • Unityのバージョンは6.3 LTS
    • 記事作成時点では6000.3.22f1で確認しています
  • #6の状態:倒した敵が金平糖を落とし、集めるとレベルが上がる

ボタンは9-2 テキスト、ボタン、画像の基本UI要素、並べ方は9-6 ScrollViewとLayout Groupで紹介しています。

武器に共通の親を作ろう(Weapon)

強化の「攻撃力アップ」は、持っている武器ぜんぶに効いてほしいものです。武器ごとに書き分けていると、武器を足すたびに強化のほうも直すことになります。

そこで、武器に共通の親クラスを作ります。

  • SurvivorsLike/Scriptsに「Weapon」を作る
using UnityEngine;

// 武器に共通する部分。強化はこの型を通してまとめて効かせる
public abstract class Weapon : MonoBehaviour
{
    [SerializeField] private WeaponData sourceData;

    // 遊んでいる間だけのコピー。強化はこちらを書き換える
    public WeaponData Data { get; private set; }

    protected virtual void Awake()
    {
        Data = Instantiate(sourceData);
    }
}

Instantiateでコピーしないと、強化がアセットに残って戻りません

ScriptableObjectはプロジェクトに保存されているファイルです。data.damage += 5とそのまま書き換えると、遊ぶのをやめてもその数字のままになります。次に再生したときは強くなった状態から始まり、遊ぶたびにどんどん強くなっていきます。

Instantiateでコピーを作れば、書き換わるのはコピーのほうだけです。再生をやめるとコピーは消えるので、アセットは元のままです。

SpoonThrowerとWhipperOrbitを、この親から作られるように直します。

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

// 一定間隔で、一番近い敵に計量スプーンを投げる
public class SpoonThrower : Weapon
{
    [SerializeField] private LayerMask enemyLayer;

    private SpoonData spoon; // 親が作ったコピーを、スプーン用として見たもの
    private ObjectPool<Spoon> pool;
    private ContactFilter2D filter;
    private readonly List<Collider2D> found = new List<Collider2D>();
    private float timer;

    protected override void Awake()
    {
        base.Awake(); // 先に親にコピーを作ってもらう
        spoon = (SpoonData)Data;

        filter = new ContactFilter2D
        {
            useLayerMask = true,
            layerMask = enemyLayer,
            useTriggers = true,
        };

        pool = new ObjectPool<Spoon>(
            createFunc: () => Instantiate(spoon.bulletPrefab, transform.parent),
            actionOnGet: bullet => bullet.gameObject.SetActive(true),
            actionOnRelease: bullet => bullet.gameObject.SetActive(false),
            actionOnDestroy: bullet => { if (bullet != null) Destroy(bullet.gameObject); },
            defaultCapacity: 20,
            maxSize: 200);
    }

    private void Update()
    {
        timer += Time.deltaTime;
        if (timer < spoon.interval)
        {
            return;
        }
        timer -= spoon.interval;

        Transform nearest = FindNearestEnemy();
        if (nearest == null)
        {
            return;
        }

        Vector2 toEnemy = (Vector2)nearest.position - (Vector2)transform.position;
        if (toEnemy.sqrMagnitude < 0.0001f)
        {
            return;
        }

        // 本数のぶんだけ、少しずつ角度をずらして投げる
        for (int i = 0; i < spoon.bulletCount; i++)
        {
            float angle = (i - (spoon.bulletCount - 1) * 0.5f) * spoon.spreadAngle;
            Vector2 direction = Quaternion.Euler(0f, 0f, angle) * toEnemy.normalized;
            pool.Get().Throw(transform.position, direction, spoon, pool);
        }
    }

    // まわりの敵を集めて、一番近いものを返す
    private Transform FindNearestEnemy()
    {
        Physics2D.OverlapCircle(transform.position, spoon.searchRadius, filter, found);

        Transform nearest = null;
        float nearestDistance = float.MaxValue;
        foreach (Collider2D collider in found)
        {
            float distance = ((Vector2)collider.transform.position - (Vector2)transform.position).sqrMagnitude;
            if (distance < nearestDistance)
            {
                nearestDistance = distance;
                nearest = collider.transform;
            }
        }
        return nearest;
    }
}
  • protected override Awake
    • base.Awake()を先に呼ぶのを忘れないでください。コピーを作るのは親の仕事です
  • (SpoonData)Data
    • 親はWeaponDataとしてコピーを持っています。スプーンの欄(飛ぶ速さなど)を使いたいので、SpoonDataとして見直しています
  • 投げる角度をずらす
    • 3本なら-15度・0度・+15度のように、真ん中をはさんで左右に散る計算にしています

SpoonDataに本数と広がりを足します。

using UnityEngine;

// 投げる武器(計量スプーン)だけの性能
[CreateAssetMenu(fileName = "SpoonData", menuName = "SurvivorsLike/Spoon Data")]
public class SpoonData : WeaponData
{
    public Spoon bulletPrefab;       // 飛ばすもの
    public float bulletSpeed = 8f;   // 飛ぶ速さ
    public float searchRadius = 10f; // 狙う範囲
    public int bulletCount = 1;      // 1回に投げる本数
    public float spreadAngle = 15f;  // 本数が増えたときの、1本ごとの角度
}

WhipperOrbitも同じで、Weaponを親にして、dataと書いていたところをwhipperに置き換えるだけです。

using System.Collections.Generic;
using UnityEngine;

// プレイヤーの周りでホイッパーを回す(当たり判定は Whipper のほうが持つ)
public class WhipperOrbit : Weapon
{
    [SerializeField] private Whipper headPrefab;

    private WhipperData whipper; // 親が作ったコピーを、ホイッパー用として見たもの
    private readonly List<Transform> heads = new List<Transform>();
    private float angle;
    private float timer;
    private bool isOut;

    protected override void Awake()
    {
        base.Awake();
        whipper = (WhipperData)Data;
    }

    private void Start()
    {
        for (int i = 0; i < whipper.orbitCount; i++)
        {
            Whipper head = Instantiate(headPrefab, transform.parent);
            head.Setup(whipper);
            heads.Add(head.transform);
        }
        Switch(true);
    }

    private void Update()
    {
        timer -= Time.deltaTime;
        if (timer <= 0f)
        {
            Switch(!isOut);
        }
        if (!isOut)
        {
            return;
        }

        angle += 360f / whipper.orbitSeconds * Time.deltaTime;

        for (int i = 0; i < heads.Count; i++)
        {
            float headAngle = angle + 360f / heads.Count * i;
            float radian = headAngle * Mathf.Deg2Rad;
            Vector2 offset = new Vector2(Mathf.Cos(radian), Mathf.Sin(radian)) * whipper.orbitRadius;

            heads[i].position = (Vector2)transform.position + offset;
            heads[i].up = -offset;
        }
    }

    private void Switch(bool next)
    {
        isOut = next;
        timer = next ? whipper.activeSeconds : whipper.restSeconds;
        angle = 0f;

        foreach (Transform head in heads)
        {
            head.gameObject.SetActive(next);
        }
    }
}

Source Dataを入れ直す

SpoonThrowerとWhipperOrbitは、データの欄を親のWeaponに移したので、欄の名前がDataからSource Dataに変わりました。名前が変わった欄は空っぽになるので、このまま再生するとエラーになります。

  • HierarchyでPlayerを選ぶ
  • Spoon ThrowerのSource Dataに、#5で作ったSpoonDataのアセットを入れる
  • Whipper OrbitのSource Dataに、WhipperDataのアセットを入れる

移動速度を外から上げられるようにしよう

PlayerMoveに、メソッドを1つ足します。

    // 強化で呼ばれる。移動速度を上げる
    public void AddSpeed(float amount)
    {
        moveSpeed += amount;
    }

3つ目の武器・ストレーナーを作ろう

足元の丸い範囲に粉をふるい落として、中にいる敵ぜんぶにダメージを与える武器です。当たり判定を持たず、一定間隔でまわりを調べる作りにします。

  • SurvivorsLike/Scriptsに「StrainerData」を作る
using UnityEngine;

// 範囲攻撃の武器(ストレーナー)だけの性能
[CreateAssetMenu(fileName = "StrainerData", menuName = "SurvivorsLike/Strainer Data")]
public class StrainerData : WeaponData
{
    public float radius = 2.5f;      // 粉がかかる範囲
    public float effectSeconds = 0.35f; // 粉が見えている時間
}
  • 続けて「StrainerArea」を作る
using System.Collections.Generic;
using UnityEngine;

// 一定間隔で、足元の丸い範囲にいる敵ぜんぶにダメージを与える
public class StrainerArea : Weapon
{
    [SerializeField] private LayerMask enemyLayer;
    [SerializeField] private SpriteRenderer effect; // 粉が広がる見た目

    private StrainerData strainer;
    private ContactFilter2D filter;
    private readonly List<Collider2D> found = new List<Collider2D>();
    private float timer;
    private float effectTimer;

    protected override void Awake()
    {
        base.Awake();
        strainer = (StrainerData)Data;

        filter = new ContactFilter2D
        {
            useLayerMask = true,
            layerMask = enemyLayer,
            useTriggers = true,
        };
        effect.transform.localScale = Vector3.one * strainer.radius * 2f;
        effect.gameObject.SetActive(false);
    }

    private void Update()
    {
        if (effect.gameObject.activeSelf)
        {
            effectTimer -= Time.deltaTime;
            if (effectTimer <= 0f)
            {
                effect.gameObject.SetActive(false);
            }
        }

        timer += Time.deltaTime;
        if (timer < strainer.interval)
        {
            return;
        }
        timer -= strainer.interval;

        Physics2D.OverlapCircle(transform.position, strainer.radius, filter, found);
        foreach (Collider2D collider in found)
        {
            if (collider.TryGetComponent(out Enemy enemy))
            {
                enemy.TakeDamage(strainer.damage);
            }
        }

        effect.gameObject.SetActive(true);
        effectTimer = strainer.effectSeconds;
    }
}
StrainerAreaのポイント
  • 当たり判定(Collider)を使っていない
    • Physics2D.OverlapCircleはその瞬間の範囲を調べる命令です。#4で一番近い敵を探したときと同じものを、ダメージにも使っています
    • 「触れ続けている間ずっと」ではなく「3秒に1回ドン」なので、こちらのほうが素直に書けます
  • 見た目は別のGameObject
    • 粉が広がる絵を0.35秒だけ出しています。当たる範囲と同じ大きさにしておくと、どこまで効いたかが目で分かります
    • 短すぎると、当たったことに気づけません。0.1秒台だと1コマだけ光って終わりでした

ストレーナーを置こう

  • HierarchyでPlayerを右クリック>Create Empty、名前を「StrainerEffect」にする
    • Add Component>Sprite Renderer、Sprite:strainer_area、Order in Layer:-1(敵より奥)
    • Color のアルファ(透明度)を半分ぐらいに下げる
  • HierarchyでPlayerを選び、Add Component>StrainerArea
    • Source Data:StrainerDataのアセット、Enemy Layer:Enemy、Effect:さっきのStrainerEffect
    • Inspectorの左のチェックを外して、最初は止めておく(強化で手に入る武器なので)
PlayerのStrainer Area。チェックが外れていて、Source DataにStrainerDataが入っている
  • SurvivorsLike/Dataで右クリック>Create>SurvivorsLike>Strainer Data、名前を「StrainerData」にする
項目値
Weapon Nameストレーナー
Iconicon_strainer
Damage10
Interval3
Radius2.5
Effect Seconds0.35

強化のデータを作ろう(UpgradeData)

強化も武器と同じで、種類ごとに効き方が違うだけです。#5の武器データと同じように、共通の部分を親にします。

  • SurvivorsLike/Scriptsに「UpgradeData」を作る
using UnityEngine;

// 強化1つ分。どう効くかは、種類ごとの子クラスが決める
public abstract class UpgradeData : ScriptableObject
{
    public string upgradeName = "強化";
    [TextArea] public string description = "説明";
    public Sprite icon;

    // カードを押したときに呼ばれる
    public abstract void Apply(GameObject player);

    // 今もらえる強化かどうか(もう持っているものを出さないため)
    public virtual bool CanTake(GameObject player) => true;
}

ここから4種類を作ります。どれも短いです。

using UnityEngine;

// 砂糖コーティング:持っている武器ぜんぶの攻撃力を上げる
[CreateAssetMenu(fileName = "PowerUpgrade", menuName = "SurvivorsLike/Upgrade/Power")]
public class PowerUpgrade : UpgradeData
{
    public int addDamage = 5;

    public override void Apply(GameObject player)
    {
        foreach (Weapon weapon in player.GetComponents<Weapon>())
        {
            weapon.Data.damage += addDamage;
        }
    }
}
using UnityEngine;

// バターましまし:移動速度を上げる
[CreateAssetMenu(fileName = "SpeedUpgrade", menuName = "SurvivorsLike/Upgrade/Speed")]
public class SpeedUpgrade : UpgradeData
{
    public float addSpeed = 1f;

    public override void Apply(GameObject player)
    {
        player.GetComponent<PlayerMove>().AddSpeed(addSpeed);
    }
}
using UnityEngine;

// 計量スプーンを1本増やす
[CreateAssetMenu(fileName = "SpoonUpgrade", menuName = "SurvivorsLike/Upgrade/Spoon")]
public class SpoonUpgrade : UpgradeData
{
    public override void Apply(GameObject player)
    {
        SpoonData spoon = (SpoonData)player.GetComponent<SpoonThrower>().Data;
        spoon.bulletCount++;
    }
}
using UnityEngine;

// ストレーナーを手に入れる(止めてあるStrainerAreaを動かす)
[CreateAssetMenu(fileName = "StrainerUpgrade", menuName = "SurvivorsLike/Upgrade/Strainer")]
public class StrainerUpgrade : UpgradeData
{
    public override void Apply(GameObject player)
    {
        player.GetComponent<StrainerArea>().enabled = true;
    }

    // すでに動いているなら、もう出さない
    public override bool CanTake(GameObject player) => !player.GetComponent<StrainerArea>().enabled;
}
  • PowerUpgradeが短い理由
    • GetComponents<Weapon>()で、Playerに付いている武器を種類を問わず全部まとめて取り出しています
    • 武器を足しても、この強化は直さなくて済みます。これが親クラスを作った見返りです
  • CanTake
    • ストレーナーだけは2回もらっても意味がないので、CanTakeを上書きして、動き出したあとは候補から外れるようにしています

強化のアセットを作ろう

SurvivorsLike/DataにUpgradesフォルダを作って、右クリック>Create>SurvivorsLike>Upgradeの中のPower・Speed・Spoon・Strainerから4つ作ります。

アセットUpgrade NameDescriptionIcon
PowerUpgrade砂糖コーティング武器ぜんぶの攻撃力が上がるicon_power
SpeedUpgradeバターましまし動く速さが上がるicon_speed
SpoonUpgrade計量スプーン追加投げるスプーンが1本増えるicon_spoon
StrainerUpgradeストレーナー足元に粉をふるって、まわりの敵を攻撃するicon_strainer
SurvivorsLike/Data/Upgradesに並んだ4つの強化のアセットと、PowerUpgradeのInspector

日本語のフォントを用意しよう

強化の名前は日本語です。ところが#6で入れたTMP Essentialsのフォントは英数字しか持っていないので、このままでは□(いわゆる豆腐)が並ぶだけになります。

  • 配布素材のzipに入っているNotoSansJP.ttfを、SurvivorsLike/Fontsに入れる
    • 自由に使えて、再配布も出来るフォントです(SIL Open Font License)
  • ProjectウインドでNotoSansJPを右クリック>Create>TextMeshPro>Font Asset>SDFで、フォントアセットを作る
    • この作り方だと、Atlas Population Mode:Dynamic(使う文字をその場で足していく)のフォントアセットになります
    • 元のNotoSansJP.ttfは、Dynamicのフォントアセットが文字を読み込むのに使うので、消さないでください
  • Edit>Project Settings>TextMesh Pro>SettingsのDefault Font Assetに、作ったフォントアセットを入れる
  • すでに作ってあるテキスト(#6のLevelTextなど)は、InspectorのFont Assetを入れ替える

日本語は文字の種類が多いので、フォントアセットはDynamicで作るのがポイントです。手順のくわしい話は9-3 TextMeshProで美しいテキストを表示しようにあります。

スライム

カードの文字が□□□になっていたら、だいたいフォントのせいです・・・Default Font Assetの入れ替えを忘れがち

3択のパネルを作ろう

カード1枚ぶんのスクリプト

  • SurvivorsLike/Scriptsに「UpgradeCard」を作る
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

// カード1枚。中身を書き換えて、押されたら知らせる
public class UpgradeCard : MonoBehaviour
{
    [SerializeField] private Image icon;
    [SerializeField] private TextMeshProUGUI nameText;
    [SerializeField] private TextMeshProUGUI descriptionText;
    [SerializeField] private Button button;

    private UpgradeData data;
    private Action<UpgradeData> onChosen;

    public void Show(UpgradeData newData, Action<UpgradeData> newOnChosen)
    {
        data = newData;
        onChosen = newOnChosen;

        icon.sprite = data.icon;
        nameText.text = data.upgradeName;
        descriptionText.text = data.description;

        button.onClick.RemoveAllListeners();
        button.onClick.AddListener(() => onChosen(data));

        gameObject.SetActive(true);
    }
}
  • RemoveAllListeners
    • カードは使い回すので、前に入れた「押したときの処理」が残っていると、2回・3回と呼ばれます
    • 入れる前に消す、をセットで書きます

画面に並べる

  • Canvasの上で右クリック>UI>Image、名前を「UpgradePanel」にする(暗幕)
    • Anchor Presetsでstretch・stretchをAltキーを押しながら選び、画面いっぱいに広げる
    • Source Image:ui_white、Color:黒、アルファ(A)を180ぐらい
  • UpgradePanelの上で右クリック>Create Empty、名前を「Cards」にする
    • Anchor Presetsでmiddle・center、Width:1500、Height:520
    • Add Component>Horizontal Layout Group
      • Spacing:40、Child Alignment:Middle Center
      • Child Force Expandの Width・Height のチェックを外す
  • Cardsの上で右クリック>UI>Button – TextMeshPro、名前を「Card」にする
    • 中のText (TMP)は使わないので削除する
    • Rect TransformのWidth:420、Height:520にする
    • Add Component>Layout Element、Preferred Width:420、Preferred Height:520
    • Cardの中にUI>Image(名前:Icon)と、UI>Text – TextMeshProを2つ(Name・Description)を作って好きに並べる
    • CardにAdd Component>UpgradeCard、4つの欄にIcon・Name・Description・Card自身(Button)を入れる
  • Cardを2回コピーして、3枚にする
Hierarchy。Canvasの下にUpgradePanel>Cards>Cardが3枚並んでいる
Horizontal Layout Groupのポイント

Horizontal Layout Groupを使うと、カードの位置を1枚ずつ決めなくて済みます。Cardsの下に入れた分だけ、自動で横に等間隔で並びます。1枚だけ出すときも真ん中にそろうので、「3枚出す/2枚しか出せない」が混ざっても崩れません。

カードが小さいままのときは、Control Child Sizeを見てください。新しく付けたHorizontal Layout GroupはWidth・Heightのチェックが外れていて、Layout Elementの大きさは使われず、カードのRect TransformのWidth・Heightのまま並びます。上の手順でWidth・Heightを420・520にしてあればOKです。

レベルアップでゲームを止めよう(UpgradeView)

  • SurvivorsLike/Scriptsに「UpgradeView」を作る
using System.Collections.Generic;
using UnityEngine;

// レベルアップでゲームを止めて、3枚のカードから1つ選ばせる
public class UpgradeView : MonoBehaviour
{
    [SerializeField] private PlayerLevel playerLevel;
    [SerializeField] private GameObject panel;
    [SerializeField] private UpgradeCard[] cards;
    [SerializeField] private UpgradeData[] allUpgrades;

    private readonly List<UpgradeData> candidates = new List<UpgradeData>();
    private int waiting; // まだ選んでいないレベルアップの回数

    private void OnEnable()
    {
        playerLevel.LevelUp += OnLevelUp;
    }

    private void OnDisable()
    {
        playerLevel.LevelUp -= OnLevelUp;
    }

    private void OnLevelUp()
    {
        waiting++;
        if (!panel.activeSelf)
        {
            ShowNext();
        }
    }

    private void ShowNext()
    {
        // 今もらえるものだけを集めて、前から3つ使う(順番はシャッフルする)
        candidates.Clear();
        foreach (UpgradeData upgrade in allUpgrades)
        {
            if (upgrade.CanTake(playerLevel.gameObject))
            {
                candidates.Add(upgrade);
            }
        }
        for (int i = candidates.Count - 1; i > 0; i--)
        {
            int j = Random.Range(0, i + 1);
            (candidates[i], candidates[j]) = (candidates[j], candidates[i]);
        }

        for (int i = 0; i < cards.Length; i++)
        {
            if (i < candidates.Count)
            {
                cards[i].Show(candidates[i], Choose);
            }
            else // 出せるものが3つ未満のとき
            {
                cards[i].gameObject.SetActive(false);
            }
        }

        panel.SetActive(true);
        Time.timeScale = 0f; // ゲームを止める
    }

    private void Choose(UpgradeData upgrade)
    {
        upgrade.Apply(playerLevel.gameObject);
        waiting--;

        if (waiting > 0)
        {
            ShowNext(); // 一度に2レベル上がったときは、続けてもう1回選ぶ
            return;
        }

        panel.SetActive(false);
        Time.timeScale = 1f; // 再開する
    }
}
UpgradeViewのポイント
  • Time.timeScale = 0
    • 時間の進み方を0にすると、Time.deltaTimeが0になり、物理も止まります。敵も武器もピタッと止まります
    • UIのボタンは止まりません。押す処理は時間と関係なく動くので、止めたままカードを選べます
  • 同じカードを2枚出さない
    • 候補のリストをシャッフルしてから前から3つ取っています。1つのリストから取るので、同じものが2枚並ぶことはありません
  • waitingで数えておく
    • 金平糖をまとめて拾うと、1回のレベルアップ処理で2回も3回もLevelUpが起きます
    • 数えておいて、選ぶたびに1つ減らし、残っていればもう1回出します
  • HierarchyでCanvasを選び、Add Component>UpgradeView
    • Player Level:Player、Panel:UpgradePanel
    • Cards:サイズを3にして、3枚のCardを入れる
    • All Upgrades:サイズを4にして、作った強化のアセットを4つ入れる
  • UpgradePanelのInspectorの左のチェックを外して、最初は非表示にしておく
CanvasのInspector。Upgrade ViewにPlayer Level・Panel・カード3枚・強化4つが入っている
スライム

レベルが上がるたびにワクワクしますね!

フェアリー

どれを取るか悩ませるのがコツだよ。全部強いけど、今いちばん欲しいのはどれか・・・って考えさせるのが楽しいんだ!

動かして確認!

画面が暗くなり、砂糖コーティング・バターましまし・ストレーナーの3枚のカードが並んでいる

#7の動作確認

  • 経験値バーが満タンになると、ゲームが止まってカードが3枚出る
  • 敵もスプーンもホイッパーも、選ぶまでピタッと止まっている
  • カードを押すと、パネルが消えてすぐに再開する
  • 同じカードが2枚並ばない
  • 「ストレーナー」を取ると、足元に粉の輪が3秒ごとに広がり、中の敵が倒れる
  • 「ストレーナー」は、1回取ったらもう出てこない
  • 「計量スプーン追加」を取ると、スプーンが2本ずつ飛ぶ
  • 金平糖をまとめて拾って2レベル上がったときは、続けて2回選ばせてくれる
スライムちゃんの足元に粉の輪が広がり、中にいたコゲクッキーが倒れていく様子

バランスを考えよう

変えたいこと変える数字
強化の効きを強くする各アセットのAdd DamageやAdd Speed
ストレーナーの範囲を広げるStrainerDataのRadius(見た目も自動で合います)
スプーンの広がりを変えるSpoonDataのSpread Angle
強化の種類を増やすUpgradeDataを継承したクラスを1つ書いて、アセットを作り、All Upgradesに足すだけ

ホイッパーを増やす強化も、同じやり方で作れます。WhipperDataのorbitCountを1増やして、WhipperOrbitに回す本数を作り直させれば完成です。ぜひ作ってみてください。

あとがき

今回は、このジャンルの一番おいしいところ、3択の強化を作りました。武器に共通の親を作っておいたおかげで、「攻撃力アップ」が数行で書けたのが気持ちよかったです!次回は、攻撃が当たったときの手応えの演出を足したいと思います。強化で武器が強くなっても、当たったことが見えないと気持ちよさが半分になってしまうので・・・

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次