第11回 エネミーのHP、弾の攻撃力、アニメーションの追加

hiroki-o edited this page Oct 15, 2015 · 4 revisions
Clone this wiki locally

前章ではタイトルを付け、ゲームの「開始」と「終了」を明確にしました。 次は、ゲームを面白くする要素として「HP(ヒットポイント)」と「弾の攻撃力」そして「エネミーがダメージを受けた時のアニメーション」「プレイヤーの無敵アニメーション」を実装します。

11.1 HP(ヒットポイント)と攻撃力(power)の実装

まずはEnemy.csにヒットポイントを実装します。

Enemy.cs

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
	// ヒットポイント
	public int hp = 1;
	
	// Spaceshipコンポーネント
	Spaceship spaceship;
	
	IEnumerator Start ()
	{
		
		// Spaceshipコンポーネントを取得
		spaceship = GetComponent<Spaceship> ();
		
		// ローカル座標のY軸のマイナス方向に移動する
		Move (transform.up * -1);
		
		// canShotがfalseの場合、ここでコルーチンを終了させる
		if (spaceship.canShot == false) {
			yield break;
		}
		
		while (true) {
			
			// 子要素を全て取得する
			for (int i = 0; i < transform.childCount; i++) {
				
				Transform shotPosition = transform.GetChild (i);
				
				// ShotPositionの位置/角度で弾を撃つ
				spaceship.Shot (shotPosition);
			}
			
			// shotDelay秒待つ
			yield return new WaitForSeconds (spaceship.shotDelay);
		}
	}
	
	// 機体の移動
	public void Move (Vector2 direction)
	{
		GetComponent<Rigidbody2D>().velocity = direction * spaceship.speed;
	}
	
	void OnTriggerEnter2D (Collider2D c)
	{
		// レイヤー名を取得
		string layerName = LayerMask.LayerToName (c.gameObject.layer);
		
		// レイヤー名がBullet (Player)以外の時は何も行わない
		if (layerName != "Bullet (Player)") return;
		
		// 弾の削除
		Destroy(c.gameObject);
		
		// 爆発
		spaceship.Explosion ();
		
		// エネミーの削除
		Destroy (gameObject);
	}
}

次にBullet.csに攻撃力を実装します。

Bullet.cs

using UnityEngine;

public class Bullet : MonoBehaviour
{
	// 弾の移動スピード
	public int speed = 10;
	
	// ゲームオブジェクト生成から削除するまでの時間
	public float lifeTime = 1;
	
	// 攻撃力
	public int power = 1;
	
	void Start ()
	{
		// ローカル座標のY軸方向に移動する
		GetComponent<Rigidbody2D>().velocity = transform.up.normalized * speed;
		
		// lifeTime秒後に削除
		Destroy (gameObject, lifeTime);
	}
}

ヒットポイントが0になった時に爆発させる

Enemy.csのOnTriggerEnter2Dメソッド内にヒットポイントを減らしていく処理を実装していきます。 今回はたまの攻撃力の数値でヒットポイントを減らしていきます。

Enemy.cs

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
	// ヒットポイント
	public int hp = 1;
	
	// Spaceshipコンポーネント
	Spaceship spaceship;
	
	IEnumerator Start ()
	{
		
		// Spaceshipコンポーネントを取得
		spaceship = GetComponent<Spaceship> ();
		
		// ローカル座標のY軸のマイナス方向に移動する
		Move (transform.up * -1);
		
		// canShotがfalseの場合、ここでコルーチンを終了させる
		if (spaceship.canShot == false) {
			yield break;
		}
		
		while (true) {
			
			// 子要素を全て取得する
			for (int i = 0; i < transform.childCount; i++) {
				
				Transform shotPosition = transform.GetChild (i);
				
				// ShotPositionの位置/角度で弾を撃つ
				spaceship.Shot (shotPosition);
			}
			
			// shotDelay秒待つ
			yield return new WaitForSeconds (spaceship.shotDelay);
		}
	}
	
	// 機体の移動
	public void Move (Vector2 direction)
	{
		GetComponent<Rigidbody2D>().velocity = direction * spaceship.speed;
	}
	
	void OnTriggerEnter2D (Collider2D c)
	{
		// レイヤー名を取得
		string layerName = LayerMask.LayerToName (c.gameObject.layer);
		
		// レイヤー名がBullet (Player)以外の時は何も行わない
		if (layerName != "Bullet (Player)") return;
		
		// PlayerBulletのTransformを取得
		Transform playerBulletTransform = c.transform.parent;
		
		// Bulletコンポーネントを取得
		Bullet bullet =  playerBulletTransform.GetComponent<Bullet>();
		
		// ヒットポイントを減らす
		hp = hp - bullet.power;
		
		// 弾の削除
		Destroy(c.gameObject);
		
		// ヒットポイントが0以下であれば
		if(hp <= 0 )
		{
			// 爆発
			spaceship.Explosion ();
			
			// エネミーの削除
			Destroy (gameObject);
		}
	}
}

WaveプレハブのEnemyを3つ選択し、HPの値を10にします。

ゲームを再生してみましょう。プレイヤーの弾に何発か当たらないとエネミーは爆発しなくなります。

11.2 ダメージを受けた時の表現

エネミーがダメージを受けたときに、わかりやすく機体の色を赤色にします。

アニメーター - レイヤーの作成

新しいレイヤーを作成し、ダメージを受けた時の表現を実装する(完成図)
図11.1: 新しいレイヤーを作成し、ダメージを受けた時の表現を実装する(完成図)

エネミーには既にNormalという「スプライトを切り替えていく」アニメーションがあります。今回はそのNormalアニメーションに、色を変化させるアニメーションを「上書き」する形でダメージを表現します。

レイヤーの追加

レイヤーを追加します。追加した後は、「NameをDamage LayerWeightを1にしてください。

レイヤーの追加
図11.2: レイヤーの追加

レイヤー名とウェイトの変更
図11.3: レイヤー名とウェイトの変更


図11.3にBlendingという項目があり、これがOverrideWeightが1であれば「他のレイヤーで同じ色のアニメーションをしている場合でも、Damage Layerの色のアニメーションを使用する」事になります。

Damageアニメーションの作成

Enemyフォルダ上で右クリックし、Create → Animationを選択してアニメーションのアセットを作成します。名前はDamageとしてください。

フォルダを右クリックしてメニューを選択するとフォルダ下に作成される
図11.4: フォルダを右クリックしてメニューを選択するとフォルダ下に作成される

作成したらアニメーターに登録するためにDamageアニメーションをアニメーターのDamage Layer上にドラッグ&ドロップします。

次はアニメーションを作成していきます。一度Enemyのプレハブをシーン上へドラッグ&ドロップしてください。

Enemyゲームオブジェクトを選択しながら、Window → Animationを選択し、Animationウィンドウを開きます。 開いたらNormalアニメーションが選択されているので、Damageアニメーションへ変更します。

アニメーションを行うプロパティを登録します。Add CurveボタンでSprite Renderer → Colorを追加してください。

するとデフォルトでKeyが2箇所、設定されているので右側のKeyを削除します。Keyを右クリックしてDelete Keysを選択してください。

更に2つの操作を行います。 まずは、0フレーム目にあるKeyを5フレーム目に移動させます。一番上のKeyをドラッグして5フレームまで移動させてください。フレーム数の確認は左上で確認することが出来ます。 次に色の情報を変更します。Color.rを1Color.gを0Color.bを0.6Color.aを1にしてください。

インスペクターのプレビューでエネミーが赤色になっているはずです。

トリガーの作成

ダメージを受けた時のみエネミーを赤色にするため、トリガーという機能を使います。 まずアニメーター上で空のステートを作成します。アニメーターウィンドウ上で右クリックをして、Create State → Emptyを選択してください。

名前をDummyとします。ステートの名前を変更するにはインスペクター上で行います。ステートをクリックしてインスペクターを表示させましょう。

次にDummyをデフォルトステートとします。Dummyステート上で右クリックしてSet As Defaultを選択してください。デフォルトステートはゲーム再生直後に再生されるステートのことを指します。こうすることによって、ゲーム再生時、Damage LayerではDummyステートが再生され、何もアニメーションを行わない状態になります。

次は何も行わない状態からトリガーによって**Damageアニメーションを再生 → 何も行わない状態 → Damageアニメーション...**と繰り返し状態の遷移を行うようにします。 Dummy上で右クリックし、Make Transitionを選択します。

カーソルに矢印が追従するのでDamageステート上でクリックします。そうするとDummyステートからDamageステートへ何らかの条件で遷移する事ができるようになります。

同じようにDamageステートからDummyステートへのTransitionも作成しましょう。

次にトリガーを作成します。アニメーターウィンドウの左上にあるParametersタブを押してParametersに切り替え、+ボタンを押してTriggerを選択してください。

名前をDamageとします。

DummyステートからDamageステートへのTransitionをクリックし、表示されたインスペクターのConditionsを+を押して追加し、をDamageへと変更します。さらに、Has Exit Timeのチェックを外しておきましょう。DamageステートからDummyステートへのTransitionはそのままにしておきましょう。

ConditionsをDamageへ
図11.5: ConditionsをDamageへ

そのまま変更なし
図11.6: そのまま変更なし

スクリプトからトリガーのセット

ダメージを受けた時に「トリガーをセット」してダメージアニメーションを再生させます。 まずはSpaceship.csにAnimatorコンポーネントを取得するメソッドを追加します。

Spaceship.cs

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D), typeof(Animator))]
public class Spaceship : MonoBehaviour
{
    // 移動スピード
    public float speed;

    // 弾を撃つ間隔
    public float shotDelay;

    // 弾のPrefab
    public GameObject bullet;

    // 弾を撃つかどうか
    public bool canShot;

    // 爆発のPrefab
    public GameObject explosion;

    // アニメーターコンポーネント
    private Animator animator;

    void Start ()
    {
        // アニメーターコンポーネントを取得
        animator = GetComponent<Animator> ();
    }

    // 爆発の作成
    public void Explosion ()
    {
        Instantiate (explosion, transform.position, transform.rotation);
    }

    // 弾の作成
    public void Shot (Transform origin)
    {
        Instantiate (bullet, origin.position, origin.rotation);
    }

    // アニメーターコンポーネントの取得
    public Animator GetAnimator()
    {
        return animator;
    }
}

OnTriggerEnter2Dメソッド内にある、エネミーのHPをチェックしている部分を「HPが0以上であればDamageトリガーをセットする」ようにします。

Enemy.cs

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
	// ヒットポイント
	public int hp = 1;
	
	// Spaceshipコンポーネント
	Spaceship spaceship;
	
	IEnumerator Start ()
	{
		
		// Spaceshipコンポーネントを取得
		spaceship = GetComponent<Spaceship> ();
		
		// ローカル座標のY軸のマイナス方向に移動する
		Move (transform.up * -1);
		
		// canShotがfalseの場合、ここでコルーチンを終了させる
		if (spaceship.canShot == false) {
			yield break;
		}
		
		while (true) {
			
			// 子要素を全て取得する
			for (int i = 0; i < transform.childCount; i++) {
				
				Transform shotPosition = transform.GetChild (i);
				
				// ShotPositionの位置/角度で弾を撃つ
				spaceship.Shot (shotPosition);
			}
			
			// shotDelay秒待つ
			yield return new WaitForSeconds (spaceship.shotDelay);
		}
	}
	
	// 機体の移動
	public void Move (Vector2 direction)
	{
		GetComponent<Rigidbody2D>().velocity = direction * spaceship.speed;
	}
	
	void OnTriggerEnter2D (Collider2D c)
	{
		// レイヤー名を取得
		string layerName = LayerMask.LayerToName (c.gameObject.layer);
		
		// レイヤー名がBullet (Player)以外の時は何も行わない
		if (layerName != "Bullet (Player)") return;
		
		// PlayerBulletのTransformを取得
		Transform playerBulletTransform = c.transform.parent;
		
		// Bulletコンポーネントを取得
		Bullet bullet =  playerBulletTransform.GetComponent<Bullet>();
		
		// ヒットポイントを減らす
		hp = hp - bullet.power;
		
		// 弾の削除
		Destroy(c.gameObject);
		
		// ヒットポイントが0以下であれば
		if(hp <= 0 )
		{
			// 爆発
			spaceship.Explosion ();
			
			// エネミーの削除
			Destroy (gameObject);
			
		}else{
			
			spaceship.GetAnimator().SetTrigger("Damage");
			
		}
	}
}

最後にEnemyゲームオブジェクトを削除して、ゲームを再生してみましょう。 弾が当たった時にエネミーが赤色になりました。ですが徐々に赤色になり、徐々に元の色に戻るアニメーション(フェード)になっています。 もう一度、アニメーターのTransitionのインスペクターを確認しましょう。
図11.7と図11.8のようにDummyステートからDamageステートへのTransitionのFadeのつまみを限りなく近づけてみましょう。


図11.7:


図11.8:

DamageステートからDummyステートへのTransitionも同じ設定を行います。ただし、Exit Timeを1へ設定してください。


図11.9:


図11.10:

ゲームを再生してみてください。ダメージを受けた時、瞬間的に赤色になるはずです。

11.3 プレイヤーの無敵時間

ゲーム開始時にプレイヤーを点滅させ、無敵の状態を実装します。 実装方法はエネミーのダメージアニメーションの実装と同じです。

レイヤーの追加

PlayerアニメーターコントローラーをダブルクリックしてAnimatorウィンドウを開きます。

Invincible Layerを追加します。Weightを1にするのを忘れないで下さい。

アニメーションの作成

Playerプレハブをシーン上へドラッグ&ドロップしてください。

次にアニメーションのアセットを作成します。Playerフォルダを選択し右クリックをして、Create → Animationを選択しください。

作成されたアニメーションのアセット名はInvincibleとします。

InvincibleアニメーションのLoop Timeにチェックを入れてください。

Invincible LayerInvincibleアニメーションをドラッグ&ドロップし、Invincibleステートを作成します。

Create State → EmptyDummyステートを作成し、InvincibleステートからDummyステートへのTransitionを作成します。 TransitionのExit Timeは3にしてください。これは3回Invincibleアニメーションをループさせる処理になります。

次にアニメーションを作成していきます。Playerゲームオブジェクトを選択しながらAnimationウィンドウを開き、Invincibleアニメーションを選択してください。

Add CurveボタンでSprite Renderer → EnabledBox Collider 2D → Enabledの2つを追加してください


図11.11:


図11.12:

0フレーム目はオフに、60フレーム目はオンにします。ただし、Box Collider 2Dのキーは削除してください。Invincibleアニメーション再生中にコライダーを無効化し、当たり判定を発生させないことで無敵を実現させます。


図11.13:


図11.14:

これを交互に60フレームまで「オフ、オン、オフ、オン...」というようにキーを打ち込んでください。この作業を楽にする方法として、キーのコピー&ペーストがあります。キーを選択し⌘(Ctrl)+Cでコピーしたら0:10の文字があるところをマウスでクリックし⌘(Ctrl)+Vでペーストしてください。

最後にPlayerゲームオブジェクトを削除して、ゲームを再生してみてください。ゲームを開始すると点滅の無敵状態が3秒間続きます。

第11回終わり

今回はここで終了です。つまずいてしまった方はプロジェクトファイルをダウンロードして新たな気持ちで次の回へ進みましょう。

今回のプロジェクトファイルをダウンロード