UnityでFPSをつくる その9-2 [ 敵の攻撃 ]

2020年8月8日

次に、Enemy同様、弾も撃って消せるようにします。
耐久力(HP)を付け、被弾時の処理を行う為に以下の名前でスクリプトを作成し、EnemyBulletにアタッチしてください。

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

public class EnemyBulletHP : MonoBehaviour
{
    //HPゲージ
    [SerializeField]int enemyBulletHP;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
    //被ダメージ処理
    public void ReceveDamage(int damageScore)
    {
        enemyBulletHP -= damageScore;
        Debug.Log("enemyBulletHP:" + enemyBulletHP);
        //オーバーキルでHPが0を飛び越えても対処できるよう、判定を0以下にしておく
        if (enemyBulletHP <= 0)
        {
            //このスクリプトがアタッチされているオブジェクトを消す
            Destroy(gameObject);
        }
    }
}

EnemyHPスクリプトと処理は同じで、Enemyより耐久力を低く設定しました。

No.360

Player側ではスクリプトを以下のように追記します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//UIを扱う為に必要
using UnityEngine.UI;

public class SightDisplay : MonoBehaviour
{
    //Imageクラス変数を宣言。SerializeFieldでInspector上から定義
    [SerializeField] Image sight;
    //rayの長さ
    float rayLength = 20000;
    //rayが当たったオブジェクトの情報を取得する為の変数
    RaycastHit hit;
    //敵に与えるダメージの値
    int damageScore = 1;
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //カメラの原点からレイを飛ばすとプレイヤー自身のタグを取得してしまう為、Z方向にズラす
        Ray ray = new Ray(transform.position + transform.rotation * new Vector3(0, 0, 0.01f), transform.forward);
        //rayの可視化
        Debug.DrawRay(transform.position + transform.rotation * new Vector3(0, 0, 0.01f), transform.forward * rayLength, Color.yellow);
        //ray上にコライダーが存在する場合、RayCastHit型変数に情報を格納する。out修飾子を付けて、戻り値を取得せずに関数内で変数の値を操作する
        if (Physics.Raycast(ray, out hit, rayLength))
        {
            //hitに取得した情報の内、タグ名を取得
            string hitTagName = hit.transform.gameObject.tag;
            //タグ名がEnemyもしくはEnemyBulletだった場合
            if (hitTagName == "Enemy" || hitTagName == "EnemyBullet")//追記部分
            {
                //Enemyタグのオブジェクトにrayが当たった時の照準の色
                sight.color = new Color(1.0f, 0.0f, 0.0f, 1.0f);
                //マウスの左ボタンがクリックされたら
                if (Input.GetMouseButtonDown(0))
                {
                    //以下、追記部分
                    if (hitTagName == "Enemy")
                    {
                        //Enemyオブジェクトに付いているEnemyHPのReceveDamage関数を呼び出す
                        hit.collider.GetComponent<EnemyHP>().ReceveDamage(damageScore);
                    }
                    else if(hitTagName == "EnemyBullet")
                    {
                        //EnemyBulletオブジェクトに付いているEnemyBulletHPのReceveDamage関数を呼び出す
                        hit.collider.GetComponent<EnemyBulletHP>().ReceveDamage(damageScore);
                    }
                    //以上、追記部分
                }
            }
            else
            {
                //Enemyタグ以外のオブジェクトにrayが当たった時の照準の色
                sight.color = new Color(1.0f, 1.0f, 1.0f, 0.5f);
            }
        }
        else
        {
            //rayがどのオブジェクトにも当たっていない時(空を向いている時)の照準の色
            sight.color = new Color(1.0f, 1.0f, 1.0f, 0.5f);
        }
    }
}

追記部分を見ていきます。
36行目で対象タグにEnemyBulletを追加しました。

//タグ名がEnemyもしくはEnemyBulletだった場合
if (hitTagName == "Enemy" || hitTagName == "EnemyBullet")//追記部分

44~53行目はスクリプトの関数呼び出しをEnemyとEnemyBulletの場合で分けています。

//以下、追記部分
if (hitTagName == "Enemy")
{
    //Enemyオブジェクトに付いているEnemyHPのReceveDamage関数を呼び出す
    hit.collider.GetComponent<EnemyHP>().ReceveDamage(damageScore);
}
else if(hitTagName == "EnemyBullet")
{
    //EnemyBulletオブジェクトに付いているEnemyBulletHPのReceveDamage関数を呼び出す
    hit.collider.GetComponent<EnemyBulletHP>().ReceveDamage(damageScore);
}
//以上、追記部分

これにより、EnemyBulletを撃って消せるようになりました。

No.361

次に、Enemyを倒したらEnemyBulletも一緒に消えるようにします。
EnemyHPスクリプトに以下のように追記してください。

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

public class EnemyHP : MonoBehaviour
{
    //HPゲージ
    [SerializeField]int enemyHP;//追記部分

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {

    }
    //被ダメージ処理
    public void ReceveDamage(int damageScore) 
    {
        enemyHP -= damageScore;
        Debug.Log("enemyHP:" + enemyHP);
        //オーバーキルでHPが0を飛び越えても対処できるよう、判定を0以下にしておく
        if (enemyHP <= 0)
        {
            //このスクリプトがアタッチされているオブジェクトを消す
            Destroy(gameObject);//追記 他のスクリプトと表記を合わせる為に「this.」を削除
            //以下、追記部分
            //GameObject型の配列enemyBulletsに、"EnemyBullet"タグのついたオブジェクトをすべて格納
            GameObject[] enemyBullets = GameObject.FindGameObjectsWithTag("EnemyBullet");
            //foreach(配列の要素の数だけループ)を使って、
            //enemyBulletにenemyBullets配列の中身を1つずつ取り出す。
            foreach (GameObject enemyBullet in enemyBullets)
            {
                Destroy(enemyBullet);
            }
            //以上、追記部分
        }
    }
}

追記部分を見ていきます。
8行目は前回書いた際に[SerializeField]を付けていなかった為、EnemyのHPもInspectorで調整できるよう付けておきます。

//HPゲージ
[SerializeField]int enemyHP;//追記部分

30行目は前回書いた際に「this」を付けていた為、意味は変わらないのですがEnemyBulletHPスクリプト内での表記と合わせる為に消しておきました。

Destroy(gameObject);//追記 他のスクリプトと表記を合わせる為に「this.」を削除

33行目では配列を宣言して複数のオブジェクトを取得しています。

//GameObject型の配列enemyBulletsに、"EnemyBullet"タグのついたオブジェクトをすべて格納
GameObject[] enemyBullets = GameObject.FindGameObjectsWithTag("EnemyBullet");

変数の型の後ろに「[]」を付けると配列を宣言したことになり、1つの値しか代入できなかった変数に複数の値を代入できるようになります。
同じタグのオブジェクトをまとめて取得するには、今まで使ってきたGameObject.FindGameObjectWithTagのObjectの部分に「s」を付けて複数形にします。

No.362

36~39行目はforeach文を使って、配列に格納したEnemyBulletオブジェクトを1つずつ取り出し、Destroy関数で削除しています。

//foreach(配列の要素の数だけループ)を使って、
//enemyBulletにenemyBullets配列の中身を1つずつ取り出す。
foreach (GameObject enemyBullet in enemyBullets)
{
    Destroy(enemyBullet);
}

foreach文は()内の条件がtrueの場合に{}内の処理を繰り返し実行します。
enemyBulletsから取り出すオブジェクトが無くなった時点で()内の条件がfalseになり、繰り返しの処理は終了します。

No.363

以上の処理により、Enemyが消えるとEnemyBulletも消えるようになりました。

No.364

次は、Playerが被弾した際の処理を実装します。
HPゲージや画面の揺れ、点滅機能を追加していきます。

No.365

まずは、照準を実装した時のように、CanvasのImageを使って画面全体が赤くなるようにしていきます。
Imageオブジェクトを2つ用意してください。
HierarchyウィンドウからUI → Imageを選択

No.366

作成されたImage (1)を「DamageBoard」、Image (2)を「DeathStateBoard」にリネーム。
ついでに、照準用のImageも「Sight」にリネームします。

No.367

DamageBoardはHPの残りがある場合に画面を点滅させる用、DeathStateBoardはHPが「0」になったときに画面を赤くする用です。
2つとも、編集時に配置がわかりやすくなるよう、ImageコンポーネントからColorのAlpha値を「0」にして透明にします。
さらに、解像度を1920×1080で想定しているので、RectTransformのWidthとHeightを2つともその数値に合わせます。

No.368

次にHPゲージ用のImageオブジェクトを用意します。
HierarchyウィンドウからUI → Imageを選択
「RemainingHp_5」にリネームしてColorとTransformの値を以下のように変更します。

No.369

これがHPゲージのひな型になるので、「Ctrl + D」で4つ複製します。
名前は数字の部分を変更して4~1にしてください。

No.370

これを左側にずらしていき、HPゲージの見た目が完成です。

No.371

数値は以下のとおりです。

No.372

Pos Xの数値を「-16」ずつ加えて等間隔にしています。

並び終えたら、 「RemainingHp」と名付けた空のオブジェクトを作り、HPゲージ全てを子オブジェクトにしてまとめます。

No.373


このUI群をスクリプトで制御していきます。

長くなってきたので続きは次回にしたいと思います。おつかれさまでした!

FPS

Posted by kenji