Unity3Dで3Dシューティングの作り方、パート2(3Dの敵、追跡、スライダーでHPの表示、攻撃)

3Dシューティングゲームの作成の続きです。これと、あと1つチュートリアルのゲームを作ったら、オリジナルゲームの作成に取り掛かろうと思います。

敵を作成する

Unity: Survival shooter – Creating Enemy #1

Assets → Models → Characters
Zombunnyをシーンビューにドロップします。

PrefabsのHitParticlesをZombunnyオブジェクトにアタッチします。
Zombunnyオブジェクトを選択してLayerをShootableに変更します。(Yes, Change the childrenを選択します)
ZombunnyオブジェクトにAdd ComponentでRigidbodyを追加します。
RigidbodyのDragをInfinity、Angular DragをInfinity、ConstraintsのFreeze PositionのYにチェック、Freeze RotationのXとZにチェックを入れます。
続けてCapsule Colliderを追加して、CenterのYを0.8、Heightを1.5に変更します。(弾に当たったとき用のCollider)
Sphere Colliderを追加して、Is Triggerにチェックを入れて、CenterのYを0.8、Radiusを0.8に変更します。(プレイヤーに接近したとき用のCollider)
Audio Sourceを追加して、Zombunny HurtをAudio Clipで指定して、Play On Awakeのチェックをはずします。
Nav Mesh Agentを追加してRadiusを0.3、Speedを3、Stopping Distanceを1.3、Heightを1.1にします。(プレイヤーを追跡するためのコンポーネントです)

Window → Navigationを選択
Inspectorの横にNavigationが表示されます。
Zombunnyオブジェクトを選択してNavigationをBakeタブにして、Radiusを0.75、Heightを1.2、Step Heightを0.1、AdvancedのWith Inaccuracyを1に変更します。最後に右下のBakeをクリックします。

敵がプイレイヤーを追跡するための設定なのですが、どうもよくわかりません。ゲームをプレイしたときに、値を変えてどんな感じになるか試してみようと思います。

Assets → Animation
Animator Controllerを作成してEnemyACという名前に変更します。
EnemyACをZombunnyオブジェクトにアタッチします。

Assets → Models → Characters
Zombunnyを展開しておきます。

Zombunnyオブジェクトを選択して、Animatorビューに切り替えます。
Zombunnyモデルを展開した中に、Move、Idle、Deathがあるので、それらをAnimatorビューにドロップします。
AnimatorビューのMoveがオレンジになっていない場合は、右クリックでSet As Defaultを選択します。

AnimatorビューでParameters(2つのTrigger)を追加します。
PlayerDeadとDeadというTriggerを作成します。

Animatorビューに戻って、MoveからIdleへTransitionを作成します。
TransitionのConditionsをPlayerDeadにします。
Any StateからDeathへTransitionを作成します。
TransitionのConditionsをDeadにします。

Assets → Script → Enemy
EnemyMovementスクリプトをZombunnyオブジェクトにアタッチします。
EnemyMovementスクリプトは既に最小限のコードが書いてあって、敵がPlayerを見つけてNav Mesh Agentを使って追跡するようになっています。

using UnityEngine;
using System.Collections;

public class EnemyMovement : MonoBehaviour
{
    Transform player;
    //PlayerHealth playerHealth;
    //EnemyHealth enemyHealth;
    NavMeshAgent nav;


    void Awake ()
    {
        player = GameObject.FindGameObjectWithTag ("Player").transform;
        //playerHealth = player.GetComponent <PlayerHealth> ();
        //enemyHealth = GetComponent <EnemyHealth> ();
        nav = GetComponent <NavMeshAgent> ();
    }


    void Update ()
    {
        //if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)
        //{
            nav.SetDestination (player.position);
        //}
        //else
        //{
        //    nav.enabled = false;
        //}
    }
}

ここでPlayボタンを実行します。

プレイヤーが逃げても敵が追ってきます。

プレイヤーのHPの表示画面の作成

Unity: Survival shooter – Health HUD

Sceneビューを2Dに変更します。
GameObject → UI → Canvasを追加して名前をHUDCanvasに変更します。
Add ComponentでCanvas Groupを追加します。canvで探すと出てきます。
Canvas GroupのInteractableとBlocks Raycastsのチェックをはずします。

HUDCanvasを選択して右クリックしてCreate Emptyを作成します。
作成されたGameObjectをHealthUIという名前に変更します。
HealthUIのRect TransformのPos X:0 Pos Y:0 Pos Z:0、Width:75、Height:60、AnchorsはXとYともに0。四角形の枠に2つの四角形が入っているボックスをクリックしてLeftとBottomをクリックします。

HealthUIを右クリックしてUI→Imageを選択します。
ImageをHeartという名前に変更します。
HeartのWidth:30 Height:30に変更します。
Source Imageの丸ポチをクリックして、ハートマークを選択します。

HealthUIを右クリックしてUI→Sliderを選択します。
SliderをHealthSliderという名前に変更します。
HealthSliderのPos Xを95に変更します。
HealthSliderを展開してそこにあるHandle Slide Areaを削除します。

HealthSliderを選択します。
TransitionをNoneにして、Max Valueを100、スライダーのValueを100に変更します。

HealthUIを右クリックしてUI→Imageを選択します。
ImageをDamageImageという名前に変更します。
DamageImageをクリックしてドラッグしてHealthCanvasの直下に持っていきます。(HealthUIの上です)
DamageImageを選択して四角形の枠に2つの四角形が入っているボックスをクリックしてStreachとStreachの2つをクリックします。Left:0 Top:0 Pos Z:0、Right:0 Bottom:0に変更します。
Colorのところをクリックして、Aというスライダーを0にします。

DamageImageのInspectorはこんな感じになります。

プレイヤーのHPを減らす

Unity: Survival shooter – Player Health

Assets → Scripts → Player
PlayerHealthスクリプトをPlayerオブジェクトにアタッチします。
PlayerHealthスクリプトにはすでにコードが書いてあるので、
動画の解説を聞きながら、へーという感じで進みます。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class PlayerHealth : MonoBehaviour
{
    public int startingHealth = 100;
    public int currentHealth;
    public Slider healthSlider;
    public Image damageImage;
    public AudioClip deathClip;
    public float flashSpeed = 5f;
    public Color flashColour = new Color(1f, 0f, 0f, 0.1f);


    Animator anim;
    AudioSource playerAudio;
    PlayerMovement playerMovement;
    //PlayerShooting playerShooting;
    bool isDead;
    bool damaged;


    void Awake ()
    {
        anim = GetComponent <Animator> ();
        playerAudio = GetComponent <AudioSource> ();
        playerMovement = GetComponent <PlayerMovement> ();
        //playerShooting = GetComponentInChildren <PlayerShooting> ();
        currentHealth = startingHealth;
    }


    void Update ()
    {
        if(damaged)
        {
            damageImage.color = flashColour;
        }
        else
        {
            damageImage.color = Color.Lerp (damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
        }
        damaged = false;
    }


    public void TakeDamage (int amount)
    {
        damaged = true;

        currentHealth -= amount;

        healthSlider.value = currentHealth;

        playerAudio.Play ();

        if(currentHealth <= 0 && !isDead)
        {
            Death ();
        }
    }


    void Death ()
    {
        isDead = true;

        //playerShooting.DisableEffects ();

        anim.SetTrigger ("Die");

        playerAudio.clip = deathClip;
        playerAudio.Play ();

        playerMovement.enabled = false;
        //playerShooting.enabled = false;
    }


    public void RestartLevel ()
    {
        Application.LoadLevel (Application.loadedLevel);
    }
}

Playerオブジェクトを選択します。
HierarchyのHealthSliderをInspectorのHealth Sliderにアタッチします。
同じくDamageImageをInspectorのDamge Imageにアタッチします。
Death Clipの右側の丸ポチをクリックしてPlayer Deathを選択します。

Assets → Scripts → Enemy
EnemyAttackスクリプトをZombunnyオブジェクトにアタッチします。
EnemyAttackスクリプトも既にコードが書いてあるので解説の動画を見ながらへーと言う感じで進みます。楽チンですね。

using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    public float timeBetweenAttacks = 0.5f;
    public int attackDamage = 10;


    Animator anim;
    GameObject player;
    PlayerHealth playerHealth;
    //EnemyHealth enemyHealth;
    bool playerInRange;
    float timer;


    void Awake ()
    {
        player = GameObject.FindGameObjectWithTag ("Player");
        playerHealth = player.GetComponent <PlayerHealth> ();
        enemyHealth = GetComponent<EnemyHealth>();
        anim = GetComponent <Animator> ();
    }


    void OnTriggerEnter (Collider other)
    {
        if(other.gameObject == player)
        {
            playerInRange = true;
        }
    }


    void OnTriggerExit (Collider other)
    {
        if(other.gameObject == player)
        {
            playerInRange = false;
        }
    }


    void Update ()
    {
        timer += Time.deltaTime;

        if(timer >= timeBetweenAttacks && playerInRange /* && enemyHealth.currentHealth */ > 0)
        {
            Attack ();
        }

        if(playerHealth.currentHealth <= 0)
        {
            anim.SetTrigger ("PlayerDead");
        }
    }


    void Attack ()
    {
        timer = 0f;

        if(playerHealth.currentHealth > 0)
        {
            playerHealth.TakeDamage (attackDamage);
        }
    }
}

解説の動画を見終わって、Playボタンを押してみます。

すると敵が接触するとプレイヤーにダメージが加わりマヌケな感じの声が発せられます。HPのスライダーがほとんどなくなると、プレイヤーは死を迎えます。(すぐに復活します)

敵を攻撃する

Unity: Survival shooter – Harming Enemies

Assets → Scripts → Enemy
EnemyHealthスクリプトをZombunnyオブジェクトにアタッチします。
Zombunnyオブジェクトを選択してDeath ClipをZomBunny Deathにします。
EnemyHealthスクリプトを開いて動画の解説を聞きます。

using UnityEngine;

public class EnemyHealth : MonoBehaviour
{
    public int startingHealth = 100;
    public int currentHealth;
    public float sinkSpeed = 2.5f;
    public int scoreValue = 10;
    public AudioClip deathClip;


    Animator anim;
    AudioSource enemyAudio;
    ParticleSystem hitParticles;
    CapsuleCollider capsuleCollider;
    bool isDead;
    bool isSinking;


    void Awake ()
    {
        anim = GetComponent <Animator> ();
        enemyAudio = GetComponent <AudioSource> ();
        hitParticles = GetComponentInChildren <ParticleSystem> ();
        capsuleCollider = GetComponent <CapsuleCollider> ();

        currentHealth = startingHealth;
    }


    void Update ()
    {
        if(isSinking)
        {
            transform.Translate (-Vector3.up * sinkSpeed * Time.deltaTime);
        }
    }


    public void TakeDamage (int amount, Vector3 hitPoint)
    {
        if(isDead)
            return;

        enemyAudio.Play ();

        currentHealth -= amount;
            
        hitParticles.transform.position = hitPoint;
        hitParticles.Play();

        if(currentHealth <= 0)
        {
            Death ();
        }
    }


    void Death ()
    {
        isDead = true;

        capsuleCollider.isTrigger = true;

        anim.SetTrigger ("Dead");

        enemyAudio.clip = deathClip;
        enemyAudio.Play ();
    }


    public void StartSinking ()
    {
        GetComponent <NavMeshAgent> ().enabled = false;
        GetComponent <Rigidbody> ().isKinematic = true;
        isSinking = true;
        //ScoreManager.score += scoreValue;
        Destroy (gameObject, 2f);
    }
}

うーん、なんでDeathメソッドでcapsuleCollider.isTriggerをtrueにしてるんだろ?
あと、StartSinkingメソッドでRigidbodyのisKinematicをtrueにしている理由がよくわならない。。

そのうちわかったら追記します。

EnemyAttackスクリプトのコメントを外していきます。敵にHPというコンセプトを入れるだけ。

PrefabsのGunParticleを選択します。
InspectorのParticle Systemの右側の歯車をクリックしてCopy Componentを選択します。

HierarchyのPlayerを展開してGunBarrelEndを選択します。
InspectorのTransformの横にある歯車をクリックしてPaste Component As Newを選択します。
Add ComponentでLine Rendererを追加します。
Line RendererのMaterialsの中のElement 0の丸ポチをクリックしてLineRenderMaterialを選択します。
Line RendererのParametersの中のStart WidthとEnd Widthを0.05に変更します。
Line RendererのチェックをはずしてDisableにします。
Add ComponentでLightを追加します。
LightのColorを黄色っぽい色にします。
LightのチェックをはずしてDisableにします。
Add ComponentでAudio Sourceを追加します。
Audio ClipにPlayer GunShotを選択して、Player On Awakeのチェックをはずします。

Assets → Scripts → Player
PlayerShootingスクリプトをGunBarrelEndにアタッチします。
PlayerShootingスクリプトを開いて動画の解説を聞きます。

using UnityEngine;

public class PlayerShooting : MonoBehaviour
{
    public int damagePerShot = 20;
    public float timeBetweenBullets = 0.15f;
    public float range = 100f;


    float timer;
    Ray shootRay;
    RaycastHit shootHit;
    int shootableMask;
    ParticleSystem gunParticles;
    LineRenderer gunLine;
    AudioSource gunAudio;
    Light gunLight;
    float effectsDisplayTime = 0.2f;


    void Awake ()
    {
        shootableMask = LayerMask.GetMask ("Shootable");
        gunParticles = GetComponent<ParticleSystem> ();
        gunLine = GetComponent <LineRenderer> ();
        gunAudio = GetComponent<AudioSource> ();
        gunLight = GetComponent<Light> ();
    }


    void Update ()
    {
        timer += Time.deltaTime;

		if(Input.GetButton ("Fire1") && timer >= timeBetweenBullets && Time.timeScale != 0)
        {
            Shoot ();
        }

        if(timer >= timeBetweenBullets * effectsDisplayTime)
        {
            DisableEffects ();
        }
    }


    public void DisableEffects ()
    {
        gunLine.enabled = false;
        gunLight.enabled = false;
    }


    void Shoot ()
    {
        timer = 0f;

        gunAudio.Play ();

        gunLight.enabled = true;

        gunParticles.Stop ();
        gunParticles.Play ();

        gunLine.enabled = true;
        gunLine.SetPosition (0, transform.position);

        shootRay.origin = transform.position;
        shootRay.direction = transform.forward;

        if(Physics.Raycast (shootRay, out shootHit, range, shootableMask))
        {
            EnemyHealth enemyHealth = shootHit.collider.GetComponent <EnemyHealth> ();
            if(enemyHealth != null)
            {
                enemyHealth.TakeDamage (damagePerShot, shootHit.point);
            }
            gunLine.SetPosition (1, shootHit.point);
        }
        else
        {
            gunLine.SetPosition (1, shootRay.origin + shootRay.direction * range);
        }
    }
}

Playerオブジェクトを選択してPrefabをApplyします。

Playボタンを押してゲームをしてみます。
マウスクリックで銃を発射します。

敵を倒したときにUnityにエラーメッセージが表示されます。
“SetDestination” can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.NavMeshAgent:SetDestination(Vector3)
EnemyMovement:Update() (at Assets/Scripts/Enemy/EnemyMovement.cs:25)

敵が倒れたときは、敵のNavMeshAgentを無効にしないといけないらしいです。
EnemyMovementスクリプトへいき敵のNavMeshAgentを無効にするためにコメントを外します。

あと、このままではプレイヤーが倒されたときでも銃を発射し続けられるので、PlayerHealthスクリプトへいき、Playerが死んだとき銃の発射のエフェクトを止めて、銃の発射を無効にするためにコメントを外します。

今回はほとんど動画を見るだけだったので、長いようで簡単に終わりました。
ゲームのプログラム的な全体構成をしっかり理解できれば、そこそこスムーズにオリジナルゲームを作成できるかもしれません。問題は3Dモデルっぽいです。3Dモデルを買うなり単純化するなりすれば、うまくいくかな?

次回でUnity3Dで3Dシューティングの作り方のラストになります。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください