Unity(C#)初心者・入門者向けチュートリアル ひよこのたまご

AndroidやiOS向けアプリを簡単に作れるゲーム開発環境Unity(ユニティ)の使い方を、チュートリアル方式で一緒に学びましょう!

【Unity9】UNETでHPが0以下になった時、Playerを破壊する!【UNET10】

Unity5.1.1p3 Personal(2015年7月)


前回の続きです〜


今回はPlayerのHPが0以下になった時に、Playerオブジェクトを破壊する処理を作っていきます〜



UNET Part 10 - Player Destruction! - YouTube

【目標】HPが0以下になった時Playerを破壊する!

①Player_Healthスクリプトを修正

Playerを破壊するといっても実際はゲーム画面からは削除せずに、移動手段やRenderer等を非アクティブ化して破壊されたかのように見せかける手法を使います〜


それではまずは、前回までに作成したPlayer_Healthスクリプトを修正していきます〜以下ソースです〜

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

public class Player_Health : NetworkBehaviour {
	
	[SyncVar (hook = "OnHealthChanged")] private int health = 100;
	private Text healthText;
//********** 開始 **********//
	private bool shouldDie = false;
	public bool isDead = false;
	
	//死んだ時に機能するDelegateとEvent
	//Event: メソッドを登録しておき、任意のタイミングで呼び出す
	public delegate void DieDelegate();
	public event DieDelegate EventDie;
//********** 終了 **********//

	void Start () {
		healthText = GameObject.Find("Health Text").GetComponent<Text>();
		SetHealthText();
	}
	
	void Update () {
//********** 開始 **********//
		CheckCondition();
//********** 終了 **********//
	}
	
//********** 開始 **********//
	void CheckCondition ()
	{
		//HPが0になった時
		if (health <= 0 && !shouldDie && !isDead) {
			shouldDie = true;
		}
		
		//HPが0で、shouldDieがtrueの時
		if (health <= 0 && shouldDie) {
			//Eventが登録されている時
			if (EventDie != null) {
				//Event実行
				EventDie();
			}
			shouldDie = false;
		}
	}
//********** 終了 **********//
	
	void SetHealthText ()
	{
		if (isLocalPlayer) {
			healthText.text = "Health " + health.ToString();
		}
	}
	
	public void DeductHealth(int dmg)
	{
		health -= dmg;
	}
	
	void OnHealthChanged (int hlth)
	{
		health = hlth;
		SetHealthText();
	}
}

Player_Health.cs


いきなりdelegate・eventと見慣れない言葉が出てきましたが、ちょうどのタイミングでデリゲートとは【C#】で解説されていましたので、参考にさせて頂きます〜

DelegateもEventもメソッドを変数に登録しておく機能を持っているようで、今回特に使うEventはDelegateの一種で、

他のクラスにメソッドを登録しといてもらい、特定のタイミングでそれを実行する

という特徴があるようです〜


今回の例では後で出てくるPlayer_Deathクラスでメソッドを登録しておいてもらい、Player_HealthクラスのCheckConditionメソッド内でEventを実行する感じです〜


どう解説したものかと思っていたのですが分かりやすい解説記事があり大変助かりました〜kan_kikuchiさん、大変ありがとうございました〜


それではスクリプトの解説に戻ります〜


まずはUpdateメソッドからCheckConditionメソッドを呼びます〜

CheckConditionメソッドでは、healthが0以下になりshouldDieとisDeadがfalseの時に、shouldDieをtrueにします〜

shouldDieがtrueになると次のhealthが0以下&shouldDieがtrueという条件を満たすので、Eventが何か登録されていれば、EventDieを実行します〜

最後にshouldDieをfalseに戻します〜

②Player_Deathスクリプトを新規作成

続いてPlayer_Deathスクリプトを新規作成して、Playerプレハブに取り付けます〜以下ソースです〜

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

public class Player_Death : NetworkBehaviour {
	
	private Player_Health healthScript; //Player_Healthスクリプトの変数
	private Image crossHairImage; //照準のImage

	void Start () {
		//キャッシュしておく
		crossHairImage = GameObject.Find("Crosshair Image").GetComponent<Image>();
		healthScript = GetComponent<Player_Health>();
		//Eventを登録
		healthScript.EventDie += DisablePlayer;
	}
	
	//メモリーリークした時用の、安全のためのメソッド そんなに重要じゃないらしい
	//OnDisable: 消滅する時に呼ばれる
	void OnDisable()
	{
		//EventからDisablePlayerメソッドを削除
		healthScript.EventDie -= DisablePlayer;
	}
	
	//Eventで登録されるメソッド CheckConditionメソッド内で使われる
	//各コンポーネントを非アクティブ状態にする
	void DisablePlayer ()
	{
		GetComponent<CharacterController> ().enabled = false;
		GetComponent<Player_Shooting> ().enabled = false;
		GetComponent<BoxCollider> ().enabled = false;
		
		//子オブジェクトのRendererを全て格納
		Renderer[] renderers = GetComponentsInChildren<Renderer> ();
		//格納したRenderer全てを比アクティブ化
		foreach (Renderer ren in renderers) {
			ren.enabled = false;
		}
		//isDeadをtrueにすることで、CheckConditionのif文内に入らないようにする
		healthScript.isDead = true;
		
		if(isLocalPlayer){
			GetComponent<UnityStandardAssets.Characters.FirstPerson.FirstPersonController> ().enabled = false;
			crossHairImage.enabled = false;
			//Respawn Button (次回以降でPlayerを復活させるためのボタンを生成する箇所)
		}
	}
}

Player_Death.cs


crossHairImage変数は、以前作成した照準のImage(赤い点)です〜


名前がImageのままなので、「Crosshair Image」という名前に変更しましょう〜


f:id:hiyotama:20150714204523p:plain
照準の名前を「Image」→「Crosshair Image」に変更


StartメソッドでcrosshairImageとhealthScriptをキャッシュしておきます〜

また、先ほど出てきたEventの登録をこのStartメソッド内で行います〜
Eventの登録には「+=」を使います〜


続いてEvent登録したDisablePlayerメソッドを見ていきましょう〜


まずはCharacterController、Player_Shooting、BoxColliderを比アクティブ化します〜


BoxColliderは、現状のままだと敵Playerを撃っても上のほうと下のほうがColliderの判定から外れてしまうようなので、動画の最後のほうで取り付けています〜
ですのでこのタイミングで取り付けちゃいましょう〜


f:id:hiyotama:20150714210446p:plain
PlayerにBox Collider IsTrigger=true Y=2


f:id:hiyotama:20150714210833p:plain
有効範囲が広がった


続いて子オブジェクトのRendererを配列を使って全て取り出し、非アクティブ化しています〜
具体的には、Player > Capsuleに付いているMesh Rendererと、Player > FirstPersonCharacter > CubeについているMesh Rendererを非アクティブ化しています〜


続いてPlayer_HealthスクリプトのisDead変数をtrueにしています〜これによりif (health <= 0 && !shouldDie && !isDead)の条件を満たすことが無くなり、CheckConditionメソッド内が有効になることは無くなりました〜


続いて自分がプレイヤー自身である時、FirstPersonControllerとcrossHairImageを非アクティブかしています〜


以上、色々なものを非アクティブ化してきましたが、PlayerのHPが0以下になった時に実行されます〜


最後にOnDisableメソッドですが、これはUnityに備わっているAPIで、このオブジェクトが消滅する時に呼ばれます〜
「-=」を使ってDisablePlayerメソッドをEventから解除しています〜

メモリーリークした時などに不具合が生じるのを防ぐために搭載しているそうですが、あんまり起こらないからそんなに重要じゃない、けど一応付けているみたいな感じのことを言ってました〜

③結果

それでは実行して試してみましょう〜その前に、Menuシーンを開き、NetworkManagerの設定をします〜


f:id:hiyotama:20150714211419p:plain
Advanced ConfigurationとUse Network Systemをfalseに


ネットワーク遅延の必要はもう無く、接続が切れても困るため、Advanced ConfigurationとUse Network Systemをfalseにします〜


それでは開発画面とビルド画面それぞれで実行して下さい〜


f:id:hiyotama:20150714212302p:plain
敵を撃つと


f:id:hiyotama:20150714212452p:plain
死ぬ


f:id:hiyotama:20150714212554p:plain
殺された側の画面


殺されると右下に見えていたCubeオブジェクトと中央にあった照準が、画面から消え、身動きが取れなくなります〜


しかし開発画面を見てみると、Playerオブジェクトは2体残ったままです〜


f:id:hiyotama:20150714212852p:plain
殺されてもオブジェクトとしては残っている


f:id:hiyotama:20150714213044p:plain
Is Deatがtrueになり、色々と非アクティブ化している


今の状態のままもう1画面ゲーム画面を増やしてみましょう〜Hierarchyビューで適当に右クリックし、Reveal Finderを選択しFinderを開きましょう〜


f:id:hiyotama:20150714214555p:plain


Finderを開いたらビルドしたファイルをコピペして、実行して下さい〜(こんなに簡単にゲーム画面を増やせるとは知らなかったです〜)


f:id:hiyotama:20150714214856p:plain
ビルドファイルをコピペするだけ


それでは3画面目を開きます〜すると、


f:id:hiyotama:20150714215057p:plain
Player3が現れる


死んだPlayer2を飛ばして、Player3が出現しました〜

こんな感じでHPが0以下になっても、ゲーム上には残り続けてボタンを押して復活させたりしていくんだと思われます〜


今回はここまでです〜

ありがとうございました〜


【Unity9】UNETでマルチプレイヤーなオンラインゲーム開発【UNET1】
【Unity9】UNETでプレイヤーの動きを補間し、なめらかな動きを実現する【UNET1-2】
【Unity9】UNETでネットワーク越しに傾き(Rotation)を同期させる【UNET2】
【Unity9】UNETでオンライン開始時のプレイヤー生成位置を変更する【UNET2-2】
【Unity9】UNETのネットワークトラフィックを軽減し、効率化する【UNET3】
【Unity9】UNETのlatency(遅延時間)を改善する【UNET4】
【Unity9】UNETのlatency(遅延時間)を表示して、ちょっとだけ改善する【UNET4-2】
【Unity9】UNETのSyncVarのhookの使用例と、前時代の同期方法【UNET5】
【Unity9】UNETを使ってRotationを同期させる【UNET6】
【Unity9】UNETで各プレイヤーにPlayerIDを設定する【UNET7】
【Unity9】UNETで敵プレイヤーにダメージを与える!【UNET8】
【Unity9】UNETでHPを画面に表示し、Playerへダメージを与える【UNET9】
【Unity9】UNETでHPが0以下になった時、Playerを破壊する!【UNET10】
【Unity9】UNETで死んだPlayerを生き返らせる!【UNET11】
【Unity9】UNETでゾンビAIを出現させる!【UNET12】
【Unity9】UNETでゾンビ生成時にユニークなIDを付ける!【UNET13】
【Unity9】UNETでゾンビを撃つ!【UNET14】
【Unity9】UNETでゾンビに攻撃させる!【UNET15】
【Unity9】UNETでゾンビの動きをスムーズにシンクロさせる!【UNET16】
【Unity9】UNETでゾンビ発生地点を増やす【UNET17】
【Unity9】UNETで発生したバグを取り除く【UNET18】
【Unity9】Unity MultiPlayerを使ってネットワーク越しにマッチメイキング!【UNET19】
【Unity9】GUIを改善して、Network Managerを見やすくする!【UNET20】
【Unity9】表示したGUI(Network Manager)を機能させる!【UNET20-2】
【Unity9】UNETでAnimationを同期させる!【UNET21】