Unity初心者が2Dタワーディフェンスを作っています。今回は前回に引き続き砲台のアップグレード機能です。コマゴマとした機能を追加していきます。
- Mac mini (M1, 2020)
- Unity 2022.3.36f1
砲台設置直後に情報パネルを表示する
現在の仕様では、砲台を設置した直後に砲台情報パネルが表示されません。設置したらすぐに情報パネルを表示したいですね。
TurretGeneratorスクリプトを修正します。たった1行の追加、とても簡単でした。
public class TurretGenerator : MonoBehaviour
{
    // ...省略...
    /// <summary>
    /// 砲台生成
    /// </summary>
    /// <param name="gridPos"></param>
    private void GenerateTurret(Vector3Int gridPos)
    {
        // 砲台が選択されていなければ何もしない
        if (selectedTurretData == null)
        {
            Debug.Log("砲台が選択されていません");
            return;
        }
        // 配置済みの場合は処理を中断
        if (occupiedCells.Contains(gridPos))
        {
            Debug.Log("このセルにはすでに砲台が配置されています");
            return;
        }
        // クリックした位置に砲台を配置
        GameObject turret = Instantiate(turretPrefab, gridPos, Quaternion.identity);
        // 砲台の位置がタイルの左下を 0,0 として生成しているので、タイルの中央にくるように位置を調整
        turret.transform.position = new Vector2(turret.transform.position.x + 0.5f, turret.transform.position.y + 0.5f);
        // TurretControllerを取得する
        TurretController turretController = turret.GetComponent<TurretController>();
        // 砲台データの初期化
        turretController.InitializeTurret(selectedTurretData, 1);
        Debug.Log($"生成された砲台: {selectedTurretData.name}");
        // ここから
        turretController.OnTurretClicked(); // マップの砲台がクリックされたときの処理
        // ここまで
        // 配置されたセルを登録
        occupiedCells.Add(gridPos);
        // 砲台を設置したら選択をリセット
        selectedTurretData = null;
        selectedTurretIcon.SetActive(false); // 追随アイコンを非表示
    }
    // ...省略...
}GenerateTurretメソッド内でOnTurretClickedを実行します。OnTurretClickedはマップ上の砲台がクリックされたときの処理を実行するメソッドで、情報パネルを表示してくれます。
マップ上の砲台にレベルを表示する
砲台のレベルはサイドバーにも表示されるのですが、マップ上の砲台にもレベルをあらわすマークを表示したいんです。以下のように「∧」の数で表現してみます。

level1.png〜level5.pngの画像を用意します。

これらを「Assets > Images > Icon」にドラッグ&ドロップしてインポートします。
「Assets > Prefabs > Turret」をダブルクリックしてプレハブの編集モードに入ります。
Turretオブジェクトの配下に「2D Object > Sprites > Square」オブジェクトを作成します。名前を「LevelIcon」に変更します。
これがレベルアイコンを表示するためのオブジェクトです。
表示の重なり順の設定です。
LevelIconのインスペクターで「Sprite Renderer > Additional Settings > Sorting Layer」を「Object」に、「Order in Layer」を「2」に設定します。これで砲台の前面にレベルアイコンが表示されるようになります。

レベルアイコンの位置と色の設定をします。
インスペクターの「Sprite Renderer > Sprite」にlevel5.pngをドラッグ&ドロップしてアサインします。「Rect Transform」で位置とスケールを調整し、「Sprite Renderer > Color」で色を設定します。

TurretControllerスクリプトを修正します。
public class TurretController : MonoBehaviour
{
    // ...省略...
    // ここから
    [SerializeField] private SpriteRenderer levelIconSpriteRenderer; // レベルアイコンのSpriteRenderer
    [SerializeField] private Sprite[] levelIcons; // レベルアイコンの配列
    // ここまで
    // ...省略...
    // ...省略...
    /// <summary>
    /// 砲台データを初期化
    /// </summary>
    public void InitializeTurret(TurretSetting.TurretData data, int level)
    {
        turretData = data;
        turretLvData = DBManager.instance.turretSetting.GetTurretData(data.id, level);
        attackPower = turretLvData.attackPower; // 攻撃力を設定
        attackInterval = turretLvData.attackInterval; // 攻撃間隔を設定
        attackRange.radius = turretLvData.attackRange; // 攻撃範囲を設定
        turretHeadSpriteRenderer.sprite = turretData.turretHeadSprite; // 砲身の画像を設定
        // ここから
        // レベルアイコンを更新
        if (level >= 1 && level <= levelIcons.Length)
        {
            levelIconSpriteRenderer.sprite = levelIcons[level - 1];
        }
        else
        {
            Debug.LogWarning($"レベル {level} のアイコンが設定されていません");
        }
        // ここまで
        Debug.Log($"砲台を初期化: {turretData.name}");
    }
    // ...省略...
}InitializeTurretメソッド内にレベルアイコンを更新する処理を入れました。
「Assets > Prefabs > Turret」をダブルクリックしてプレハブの編集モードに入ります。
Turretオブジェクトのインスペクターの「Turret Controller (Script) > Level Icon Sprite Renderer」に「LevelIcon」オブジェクトをドラッグ&ドロップしてアタッチします。また、「Level Icons」にlevel1.png〜level5.pngをドラッグ&ドロップしてアタッチします。

以上で、マップ上の砲台にレベルアイコンが表示されるようになりました。いえい。
砲台のアップグレードに時間をかける
現在のところ、強化ボタンを押すと瞬時に砲台がアップグレードされるのですが、時間をかけてアップグレードするようにします。アップグレード中は敵を攻撃できないといういぢわる機能の実装です。
TurretControllerスクリプトを修正します。
public class TurretController : MonoBehaviour
{
    // ...省略...
    // ここから
    /// <summary>
    /// 砲台をアップグレードするコルーチン
    /// </summary>
    //public void UpgradeTurret()
    public IEnumerator UpgradeTurretCoroutine()
    // ここまで
    {
        int newLevel = turretLvData.level + 1; // アップグレード後のレベル
        // ここから
        float upgradeTime = newLevel * 6.0f - 11; // 待機時間
        Debug.Log($"アップグレード開始: レベル {turretLvData.level} → {newLevel} (所要時間 {upgradeTime} 秒)");
        yield return new WaitForSeconds(upgradeTime); // 指定時間待機
        // ここまで
        // アップグレード後の砲台情報を取得
        TurretSetting.TurretLvData newTurretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, newLevel);
        if (newTurretLvData == null)
        {
            Debug.LogWarning($"砲台 {turretData.id} のレベル {newLevel} のデータが見つかりません");
            // ここから
            //return;
            yield break;
            // ここまで
        }
        turretLvData = newTurretLvData; // データを更新
        InitializeTurret(turretData, newLevel); // 砲台を初期化
        // ...省略...
    }
}UpgradeTurretメソッドをコルーチンに変更しました。
アップグレードの待機時間(upgradeTime)はTurretSettingに持たせることもできるのですが、今回はnewLevel * 6.0f - 11で計算することにしました。レベル2へのアップグレードなら1秒、レベル3へのアップグレードなら7秒ですね。
SideBarManagerスクリプトを修正します。
public class SideBarManager : MonoBehaviour
{
    // ...省略...
    /// <summary>
    /// 選択された砲台をアップグレードする
    /// </summary>
    private void UpgradeSelectedTurret()
    {
        if (selectedTurret != null)
        {
            // ここから
            //selectedTurret.UpgradeTurret();
            StartCoroutine(selectedTurret.UpgradeTurretCoroutine());
            // ここまで
        }
    }
}UpgradeTurretメソッドを呼び出していた部分を、UpgradeTurretCoroutineコルーチンの開始に変更します。
アップグレード中は攻撃しない
砲台のアップグレード中は敵を攻撃しないようにします。
TurretControllerスクリプトを修正します。
public class TurretController : MonoBehaviour
{
    // ...省略...
    // ここから
    private bool isUpgrading = false; // アップグレード中フラグ
    // ここまで
    // ...省略...
    /// <summary>
    /// 攻撃間隔管理
    /// </summary>
    public IEnumerator ManageAttacks()
    {
        Debug.Log("攻撃間隔管理");
        // 攻撃状態の間ループ処理を繰り返す
        while (isAttacking)
        {
            // ここから
            //if (targetEnemy) // ターゲットが存在する場合
            if (targetEnemy && !isUpgrading) // ターゲットが存在し、アップグレード中でない場合
            // ここまで
            {
                Attack(); // 攻撃を実行
            }
            // 次の攻撃まで待機
            yield return new WaitForSeconds(attackInterval / 60.0f);
        }
    }
    // ...省略...
    /// <summary>
    /// 砲身を敵の方向に回転させる
    /// </summary>
    private void RotateTurretHeadTowardsEnemy()
    {
        // ここから
        // ターゲットが存在しない場合またはアップグレード中の場合、何もしない
        //if (!targetEnemy) return;
        if (!targetEnemy || isUpgrading) return;
        // ここまで
        // 敵の位置と砲身の位置の差分を計算
        Vector3 direction = targetEnemy.transform.position - turretHead.position;
        // Z軸方向の回転角度を計算
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg - 90.0f;
        // 砲身を回転させる
        turretHead.rotation = Quaternion.Euler(0, 0, angle);
    }
    // ...省略...
    /// <summary>
    /// 砲台をアップグレードするコルーチン
    /// </summary>
    public IEnumerator UpgradeTurretCoroutine()
    {
        // ここから
        isUpgrading = true; // アップグレード開始
        // ここまで
        int newLevel = turretLvData.level + 1; // アップグレード後のレベル
        float upgradeTime = newLevel * 6.0f - 11; // 待機時間
        Debug.Log($"アップグレード開始: レベル {turretLvData.level} → {newLevel} (所要時間 {upgradeTime} 秒)");
        // アップグレード後の砲台情報を取得
        TurretSetting.TurretLvData newTurretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, newLevel);
        if (newTurretLvData == null)
        {
            Debug.LogWarning($"砲台 {turretData.id} のレベル {newLevel} のデータが見つかりません");
            // ここから
            isUpgrading = false; // アップグレード終了
            // ここまで
            yield break;
        }
        // ...省略...
        // ここから
        isUpgrading = false; // アップグレード終了
        // ここまで
    }
}5行目、アップグレード中かどうかをあらわす変数isUpgradingを定義します。
ManageAttacksコルーチン内で、アップグレード中でない場合のみ攻撃を実行するようにします。
RotateTurretHeadTowardsEnemyメソッド内で、アップグレード中の場合、砲身を回転させないようにします。
UpgradeTurretCoroutineコルーチン内で、isUpgradingフラグを切り替えます。
アップグレード中にインジケータを表示する
砲台のアップグレード中に、残り時間をあらわす円形のインジケータを表示します。
ヒエラルキーでCanvasの配下に「UI > Image」オブジェクトを作成します。名前を「UpgradeIndicatorCanvas」にします。これがインジケータの背景になります。
UpgradeIndicatorCanvasの子要素として「UI > Image」オブジェクトを作成します。名前を「UpgradeIndicatorFill」にします。これがインジケータの塗りつぶし部分になります。
それぞれのオブジェクトの「Image > Source Image」に白い円形の画像(circle.png)をアサインします。
「Image > Color」で色を設定します。UpgradeIndicatorCanvasは灰色、UpgradeIndicatorFillは黄色にしました。
「Rect Transform」の「Width」, 「Height」でサイズを設定します。両方とも「60」にしました。
UpgradeIndicatorFillの「Image > Image Type」を「Filled」に設定します。「Fill Method」を「Radial 360」に設定します。「Fill Origin」を「Top」に設定します。

この設定で、円形の画像が時間とともに埋められていくインジケータの準備が整いました。
UpgradeIndicatorCanvasを「Assets > Prefabs」フォルダにドラッグ&ドロップしてプレハブ化します。ヒエラルキーのUpgradeIndicatorCanvasオブジェクトは削除します。
TurretControllerスクリプトを修正します。
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor.PackageManager;
using UnityEngine;
// ここから
using UnityEngine.UI; // UIを使用するための宣言
// ここまで
public class TurretController : MonoBehaviour
{
    // ...省略...
    // ここから
    [SerializeField] private GameObject upgradeIndicatorPrefab; // インジケータのプレハブ
    private GameObject currentIndicator; // 現在のインジケータ
    // ここまで
    // ...省略...
    /// <summary>
    /// 砲台をアップグレードするコルーチン
    /// </summary>
    public IEnumerator UpgradeTurretCoroutine()
    {
        isUpgrading = true; // アップグレード開始
        // ここから
        // インジケータを生成
        currentIndicator = Instantiate(upgradeIndicatorPrefab, transform.position, Quaternion.identity);
        // 親をCanvasに設定
        Canvas canvas = FindObjectOfType<Canvas>();
        currentIndicator.transform.SetParent(canvas.transform, false);
        // ワールド座標をスクリーン座標に変換
        Vector2 screenPosition = Camera.main.WorldToScreenPoint(transform.position);
        currentIndicator.GetComponent<RectTransform>().position = screenPosition;
        // 塗りつぶしImageを取得
        Image fillImage = currentIndicator.transform.Find("UpgradeIndicatorFill").GetComponent<Image>();
        // ここまで
        int newLevel = turretLvData.level + 1; // アップグレード後のレベル
        float upgradeTime = newLevel * 6.0f - 11; // 待機時間
        Debug.Log($"アップグレード開始: レベル {turretLvData.level} → {newLevel} (所要時間 {upgradeTime} 秒)");
        // ここから
        //yield return new WaitForSeconds(upgradeTime); // 指定時間待機
        float elapsedTime = 0f; // 経過時間
        while (elapsedTime < upgradeTime)
        {
            elapsedTime += Time.deltaTime;
            fillImage.fillAmount = elapsedTime / upgradeTime; // 塗りつぶし量を更新
            yield return null;
        }
        // ここまで
        // アップグレード後の砲台情報を取得
        TurretSetting.TurretLvData newTurretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, newLevel);
        if (newTurretLvData == null)
        {
            Debug.LogWarning($"砲台 {turretData.id} のレベル {newLevel} のデータが見つかりません");
            isUpgrading = false; // アップグレード終了
            // ここから
            Destroy(currentIndicator); // インジケータを削除
            // ここまで
            yield break;
        }
        turretLvData = newTurretLvData; // データを更新
        InitializeTurret(turretData, newLevel); // 砲台を初期化
        // ...省略...
        isUpgrading = false; // アップグレード終了
        // ここから
        Destroy(currentIndicator); // インジケータを削除
        // ここまで
    }
}インジケータのプレハブを入れておく変数upgradeIndicatorPrefabと現在のインジケータを入れておく変数currentIndicatorを定義します。
UpgradeTurretCoroutine内に以下の処理を入れます。
- 28行目: プレハブからインジケータを生成
- 30-31行目: インジケータのオブジェクトをCanvasの配下に移動
- 33-34行目: ワールド座標をスクリーン座標に変換
- 36行目: 塗りつぶし画像を取得
- 43-49行目: 経過時間に応じて塗りつぶし量を更新
アップグレードが終わったら、currentIndicatorを削除します。
「Assets > Prefabs > turret」をダブルクリックしてプレハブの編集モードに入ります。
Turretオブジェクトのインスペクターで「Turret Controller (Script) > Upgrade Indicator Prefab」にUpgradeIndicatorCanvasプレハブをドラッグ&ドロップしてアサインします。

以上で、砲台のアップグレード中に円形のインジケータが表示されるようになりました。いえい。
アップグレード中はアップグレードできないようにする
砲台のアップグレード中に同じ砲台をアップグレードできないようにします。強化ボタンを押せないようにすればいいですね。
SideBarManagerスクリプトを修正します。
public class SideBarManager : MonoBehaviour
{
    // ...省略...
    // ここから
    /// <summary>
    /// 強化ボタンのinteractableを切り替え
    /// </summary>
    public void SetUpgradeButtonInteractable(bool interactable)
    {
        upgradeButton.interactable = interactable;
    }
    // ここまで
}強化ボタンのinteractableを切り替えるメソッドを追加しました。
TurretControllerスクリプトを修正します。
public class TurretController : MonoBehaviour
{
    // ...省略...
    /// <summary>
    /// 砲台をアップグレードするコルーチン
    /// </summary>
    public IEnumerator UpgradeTurretCoroutine()
    {
        isUpgrading = true; // アップグレード開始
        // インジケータを生成
        currentIndicator = Instantiate(upgradeIndicatorPrefab, transform.position, Quaternion.identity);
        // 親をCanvasに設定
        Canvas canvas = FindObjectOfType<Canvas>();
        currentIndicator.transform.SetParent(canvas.transform, false);
        // ワールド座標をスクリーン座標に変換
        Vector2 screenPosition = Camera.main.WorldToScreenPoint(transform.position);
        currentIndicator.GetComponent<RectTransform>().position = screenPosition;
        // 塗りつぶしImageを取得
        Image fillImage = currentIndicator.transform.Find("UpgradeIndicatorFill").GetComponent<Image>();
        int newLevel = turretLvData.level + 1; // アップグレード後のレベル
        float upgradeTime = newLevel * 6.0f - 11; // 待機時間
        Debug.Log($"アップグレード開始: レベル {turretLvData.level} → {newLevel} (所要時間 {upgradeTime} 秒)");
        float elapsedTime = 0f; // 経過時間
        // ここから
        SideBarManager sideBarManager = FindObjectOfType<SideBarManager>(); // SideBarManagerを取得
        if (sideBarManager != null)
        {
            sideBarManager.SetUpgradeButtonInteractable(false); // 強化ボタンを非活性化
        }
        else
        {
            Debug.LogError("SideBarManager が見つかりません");
        }
        // ここまで
        while (elapsedTime < upgradeTime)
        {
            elapsedTime += Time.deltaTime;
            fillImage.fillAmount = elapsedTime / upgradeTime; // 塗りつぶし量を更新
            yield return null;
        }
        // アップグレード後の砲台情報を取得
        TurretSetting.TurretLvData newTurretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, newLevel);
        if (newTurretLvData == null)
        {
            Debug.LogWarning($"砲台 {turretData.id} のレベル {newLevel} のデータが見つかりません");
            isUpgrading = false; // アップグレード終了
            Destroy(currentIndicator); // インジケータを削除
            yield break;
        }
        turretLvData = newTurretLvData; // データを更新
        InitializeTurret(turretData, newLevel); // 砲台を初期化
        // UIの更新
        // ここから
        //SideBarManager sideBarManager = FindObjectOfType<SideBarManager>(); // SideBarManagerを取得
        // ここまで
        if (sideBarManager != null)
        {
            sideBarManager.ShowTurretInfo(turretData, turretLvData, true, this); // 砲台情報パネルを表示
            if (newLevel < 5) // レベルが5未満なら
            {
                sideBarManager.ShowTurretUpgradeInfo(turretData, turretLvData, true); // 強化パネルを表示
            }
            else // レベルが5以上なら
            {
                sideBarManager.HideTurretUpgradeInfo(); // 強化パネルを非表示
            }
            // ここから
            sideBarManager.SetUpgradeButtonInteractable(true); // 強化ボタンを活性化
            // ここまで
        }
        else
        {
            Debug.LogError("SideBarManager が見つかりません");
        }
        isUpgrading = false; // アップグレード終了
        Destroy(currentIndicator); // インジケータを削除
    }
}UpgradeTurretCoroutine内で、アップグレードを開始したら強化ボタンを非活性化し、アップグレードが終了したら強化ボタンを活性化します。
アップグレード中に攻撃範囲を非表示にする
砲台をアップグレードすると攻撃範囲が変わることがあるので、アップグレード前の攻撃範囲が表示されたままになっているとよろしくない。というわけで、アップグレードが始まったら攻撃範囲を非表示にします。
TurretControllerスクリプトを修正します。
public class TurretController : MonoBehaviour
{
    // ...省略...
    /// <summary>
    /// 砲台をアップグレードするコルーチン
    /// </summary>
    public IEnumerator UpgradeTurretCoroutine()
    {
        isUpgrading = true; // アップグレード開始
        // ここから
        lineRenderer.enabled = false; // 攻撃範囲を非表示
        // ここまで
        // ...省略...
    }
}たった1行の変更でした。
マップをクリックしたらタイルをハイライト表示する
マップ上のどの砲台が選択されているのかをわかりやすくしたいので、クリックしたタイルをハイライトします。
ハイライト用の画像(roundedsquare.png)を用意して、「Assets > Images > Icon」にインポートします。
roundedsquare.pngはこんな感じの画像です。背景がグレーに見えますが実際は透明です。

ヒエラルキーで右クリック「2D Object > Sprites > Square」を選択してオブジェクトを作成します。名前を「TileHighlighter」にします。
TileHighlighterのインスペクターで「Sprite Renderer > Sprite」にroundedsquare.pngをドラッグ&ドロップしてアサインします。

「Transform > Scale」を調整してタイルのサイズに合うようにします。私はX: 0.4, Y: 0.4にしました。
「Sprite Renderer > Color」で色を設定します。私は半透明の白にしました。
「Sprite Renderer > Additional Settings > Sorting Layer」を「Way」に、「Order in Layer」を「1」に設定します。これで、ハイライト画像がマップの道より手前、砲台より奥に表示されます。

TileHighlighterのインスペクターで左上のチェックを外してオブジェクトを非表示にしておきます。

GamaManagerスクリプトを修正します。
public class GameManager : MonoBehaviour
{
    // ...省略...
    // ここから
    [SerializeField] public GameObject tileHighlighter; // ハイライト用オブジェクト
    private Vector2 tileSize = new Vector2(1, 1); // マス目のサイズ
    // ここまで
    // ...省略...
    /// <summary>
    /// マウスのクリックを検出
    /// </summary>
    void DetectClick()
    {
        // UIがクリックされた場合は何もしない
        if (EventSystem.current.IsPointerOverGameObject())
        {
            return;
        }
        // クリック位置のスクリーン座標をワールド座標に変換
        Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        // ここから
        // クリック位置をタイルの中央に補正
        Vector2 snappedPosition = new Vector2(
            Mathf.Floor(worldPoint.x / tileSize.x) * tileSize.x + tileSize.x / 2,
            Mathf.Floor(worldPoint.y / tileSize.y) * tileSize.y + tileSize.y / 2
        );
        // ここまで
        // クリック位置から見えるオブジェクトに対してレイキャスト(Ignore Raycastレイヤーを除外)
        RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero, Mathf.Infinity, ~LayerMask.GetMask("Ignore Raycast"));
        if (hit.collider != null) // クリックしたオブジェクトにコライダーがあれば
        {
            TurretController turret = hit.collider.GetComponent<TurretController>(); // TurretControllerを取得
            if (turret != null) // クリックしたオブジェクトが砲台ならば
            {
                turret.OnTurretClicked(); // 砲台情報を表示
                // ここから
                //return;
                snappedPosition = turret.transform.position; // 砲台の座標を取得
                // ここまで
            }
        }
        else // 砲台以外をクリックした場合
        {
            sideBarManager.HideTurretInfo(); // 砲台情報パネルを非表示
            sideBarManager.HideTurretUpgradeInfo(); // 砲台強化情報パネルを非表示
        }
        // ここから
        // ハイライトの位置を更新
        if (tileHighlighter != null)
        {
            tileHighlighter.transform.position = snappedPosition;
            tileHighlighter.SetActive(true);
        }
        // ここまで
    }
}25-28行目、クリックされたワールド座標をゲーム内のグリッドに吸着させるための計算をしています。これによって、クリック位置が常にタイルの中央に補正されます。
39-40行目、クリックしたオブジェクトが砲台だった場合、snappedPositionに砲台の座標を代入します。
51-55行目、tileHighlighterの位置をsnappedPositionに更新し表示します。
GameManagerオブジェクトのインスペクターで「Game Manager (Script) > TileHighlighter」にTileHighlighterオブジェクトをドラッグ&ドロップしてアタッチします。

以上で、マップをクリックしたときにタイルがハイライトされるようになりました。
TurretGeneratorスクリプトを修正します。
public class TurretGenerator : MonoBehaviour
{
    // ...省略...
    // ここから
    [SerializeField] private GameManager gameManager; // GameManagerの参照
    // ここまで
    // ...省略...
    /// <summary>
    /// 砲台アイコンを選択する
    /// </summary>
    public void SelectTurret(int index)
    {
        selectedTurretData = DBManager.instance.turretSetting.turretDataList[index];
        Debug.Log($"{selectedTurretData.name} を選択");
        // アイコンを表示
        selectedTurretIcon.SetActive(true);
        // 砲身のスプライトを設定
        SpriteRenderer iconRenderer = selectedTurretHead.GetComponent<SpriteRenderer>();
        iconRenderer.sprite = selectedTurretData.turretHeadSprite;
        TurretSetting.TurretLvData turretLvData = DBManager.instance.turretSetting.GetTurretData(selectedTurretData.id, 1);
        sideBarManager.ShowTurretInfo(selectedTurretData, turretLvData, false, null); // 砲台情報を表示
        sideBarManager.HideTurretUpgradeInfo(); // 砲台強化情報を非表示
        // ここから
        if (gameManager.tileHighlighter != null)
        {
            gameManager.tileHighlighter.SetActive(false);
        }
        // ここまで
    }
}サイドバーの砲台アイコンを選択したときには、タイルのハイライト表示を消します。
TurretGeneratorオブジェクトのインスペクターで「Turret Generator (Script) > Game Manager」にGameManagerオブジェクトをドラッグ&ドロップしてアサインします。

敵のHPバーを表示する
砲台のアップグレード機能とは関係ないのですが、敵のHPバーを実装するのを忘れていたのでここでやっておきましょう。
Canvasの配下に「UI > Slider」オブジェクトを作成します。名前を「HpBar」ににします。
HpBarの配下にある「Handle Slide Area」はスライダーの丸いつまみの部分です。不要なので削除します。

HpBarの「Slider > Value」のつまみを動かすとバーが動くのがわかります。

しかし、0のときにもバーは少し残るし、1にしても完全には埋まりません。これはつまみの部分を考慮した設計になっているためです。
この問題を解決するには、Fill Areaの「Rect Transform」で「Left」と「Right」の値をを0にします。また、 Fillの「Rect Transform」で「Width」を0にします。
HpBarの「Rect Transform」で「Width」と「Height」を調整してHPバーの大きさを決めます。
また、BackgroundとFillの「Image > Color」をお好みの色に変更します。定番はBackgroundが赤でFillが緑ですね。
HPバーの角が丸くなっていますが、これはBackgroundとFillの「Image > Source Image」に画像がアサインされているからです。「None」に設定すると角が四角くなります。お好みでどうぞ。
HpBarオブジェクトの「Slider > Max Value」を「1」に、「Value」も「1」にしておきます。
C#スクリプトを新規作成し、名前を「HpBar」にします。内容は次のとおりです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HpBar : MonoBehaviour
{
    [SerializeField] private Slider slider; // スライダー
    [SerializeField] private Vector3 offset; // 表示位置のオフセット
    /// <summary>
    /// HPバーの初期化
    /// </summary>
    /// <param name="maxHp">最大HP</param>
    public void InitializeHpBar(int maxHp)
    {
        slider.maxValue = maxHp;
        slider.value = maxHp;
    }
    /// <summary>
    /// HPバーの更新
    /// </summary>
    /// <param name="currentHp">現在のHP</param>
    public void UpdateHpBar(int currentHp)
    {
        slider.value = currentHp;
    }
    /// <summary>
    /// HPバーの座標更新
    /// </summary>
    public void UpdatePosition(Vector3 enemyPosition)
    {
        transform.position = Camera.main.WorldToScreenPoint(enemyPosition + offset);
    }
}HpBarスクリプトをHpBarオブジェクトにドラッグ&ドロップしてアタッチします。
HpBarオブジェクトのインスペクターで「Hp Bar (Script) > Slider」にHpBarオブジェクトをドラッグ&ドロップしてアサインします。
HpBarオブジェクトを「Assets > Prefabs」フォルダにドラッグ&ドロップしてプレハブ化します。ヒエラルキーのHpBarオブジェクトは削除します。
EnemyControllerスクリプトを修正します。
public class EnemyController : MonoBehaviour
{
    // ...省略...
    // ここから
    [SerializeField] private HpBar hpBarPrefab; // HPバーのプレハブ
    private HpBar hpBar; // HPバー
    // ここまで
    // ここから
    private void Update()
    {
        hpBar.UpdatePosition(transform.position); // HPバーの位置を更新
    }
    // ここまで
    /// <summary>
    /// 敵データを初期化
    /// </summary>
    public void InitializeEnemy(PathData selectedPath, GameManager gameManager, EnemySetting.EnemyData enemyData)
    {
        // ...省略...
        // ここから
        hpBar = Instantiate(hpBarPrefab, transform); // HPバーを生成
        hpBar.InitializeHpBar(maxHp); // HPバーの初期化
        hpBar.transform.SetParent(GameObject.Find("Canvas").transform, false); // Canvasの子にする
        // ここまで
    }
    // ...省略...
    /// <summary>
    /// ダメージ計算
    /// </summary>
    /// <param name="amount"></param>
    public void CalcDamage(int amount)
    {
        // HPの値を減算した結果値を、最小値と最大値の範囲内に収まるようにして更新
        hp = Mathf.Clamp(hp -= amount, 0, enemyData.maxHp);
        Debug.Log("残りHP : " + hp);
        // ここから
        hpBar.UpdateHpBar(hp); // HPバーの更新
        // ここまで
        if (hp <= 0) // HPが0以下になったら
        {
            DestroyEnemy(); // 敵を破壊
        }
    }
    /// <summary>
    /// 敵の破壊
    /// </summary>
    public void DestroyEnemy()
    {
        tween.Kill(); // tween変数に代入されている処理を終了する
        // ここから
        Destroy(hpBar.gameObject); // HPバーを破壊
        // ここまで
        Destroy(gameObject); // 敵の破壊
    }
    // ...省略...
}5-6行目、hpBarPrefab変数とhpBar変数を定義します。
10-13行目、毎フレームごとに敵の動きに合わせてHPバーの位置を更新します。
23-25行目、InitializeEnemyメソッド内で、プレハブからHPバーを生成し、初期化し、Canvasの子要素にします。
41行目、CalcDamageメソッド内で、HPバーの表示を更新します。
56行目、敵が破壊されたらHPバーも破壊します。
「Assets > Prefabs > Enemy」をダブルクリックしてプレハブの編集モードに入ります。
インスペクターの「Enemy Controller (Script) > Hp Bar Prefab」にHpBarプレハブをドラッグ&ドロップしてアサインします。
ゲームを実行して動作確認をします。
HPバーが敵の真ん中に表示されました。敵の上部に表示したいので位置を調整します。
「Assets > Prefabs > HpBar」をダブルクリックしてプレハブの編集モードに入ります。HpBarのインスペクターで「HpBar (Script) > Offset」を調整します。X: 0, Y: 0.6, Z: 0でいい感じに表示されるようになりました。いえい。
動作確認
最終的な動作確認をしましょう。
- 砲台設置時に情報パネルが表示されるようになりました。
- マップ上の砲台にレベルをあらわすマークが表示されるようになりました。
- 砲台のアップグレード中にインジケータが表示されるようになりました。
- マップ上の選択中の砲台がハイライト表示されるようになりました。
- 敵のHPバーが表示されるようになりました。
さいごに
砲台のアップグレードに関連する細かな機能を追加しました。
今回学んだことは、UIオブジェクトは親オブジェクトにCanvasコンポーネントがないと動かないということです。
でわでわ


 
	
コメント