今回はプレイヤーキャラクターとは別にNPC(ノンプレイヤーキャラクター)である敵をインスタンス化し登場させてみたいと思います。
前回はキャラクターの登場位置の設定とHPが0以下になった時に再度登場させる機能を作成しました。

あらかじめシーンに敵を配置する
まずは敵をプレハブからインスタンス化するのではなく、あらかじめシーン上に配置しておくようにしましょう。
シーン上に敵を配置しておき、NetworkIdentityコンポーネントを取り付けておくと、この敵はサーバー管理のゲームオブジェクトとなります。
その為、自身がホストの時にアプリケーション上の敵のHPが0以下になった時にDestoryでゲームオブジェクトを消すと、自身のシーン上から削除され、自身がクライアントであれば敵は非アクティブとなります。
シーン上に元々あるゲームオブジェクトはシーンオブジェクトと言うらしいです。
敵キャラクターの作成
シーン上に敵を配置し、
Add ComponentからNetwork→NetworkIdentityを取り付けます。
Add ComponentからNetwork→NetworkTransformを取り付けます。
Add ComponentからNetwork→NetworkAnimatorを取り付けます。
Network TransformのTransform Sync ModeはSync Transformを選択します。
NetworkIdentityはネットワーク上で取り扱う為に取り付け、NetworkTransformは他のアプリケーションと位置を同期する為、NetworkAnimatorはアニメーションを同期する為に取り付けます。
NetworkTransformやNetworkAnimatorを取り付けなくてもクライアントで位置やアニメーションが同期されているような気がしましたが、やはり少しづつズレていきますね。
試しに付けない状態でEnable Matchを押してCreate InternetMatchを選択し、他のアプリケーションからEnable MatchからFind Internet Matchボタンを押し確認すると、途中から同期が取れなくなっていくのを確認出来ました。
実際に試してみるとわかりやすいかもしれません。
敵キャラクターのAnimatorには敵用のAnimatorControllerを取り付け、IdleとWalk状態を作ります。
アニメーションパラメータにFloat型のSpeedを作成します。
Idle→WalkへはSpeedが0.1以上、
Walk→IdleへはSpeedが0.1以下、
で遷移するように条件を設定します。
ここら辺はオンラインでもオフラインでも同じなので問題はないと思います。
NetworkAnimatorのAnimatorに自身のAnimatorをドラッグ&ドロップして、Speedのチェックを入れます。
敵にはナビゲーション機能を取り付け、近くにいるプレイヤーキャラクターを追いかけるようにします。
Add ComponentからNavigation→NavMeshAgentを取り付けます。
次に敵が攻撃を受けた時ように敵の当たり判定のコライダを追加します。
Add ComponentからPhysics→Capsule Colliderを取り付け、コライダのサイズを調整し、Is Triggerにチェックを入れます。
プレイヤー検知機能の作成
敵キャラクターはナビゲーション機能を使ってプレイヤーキャラクターを追いかける為、敵キャラクターの子要素に空のゲームオブジェクトを作り、名前をSearchAreaとします。
SearchAreaにはSpherer Colliderを取り付け、プレイキャーキャラクターを検知する範囲に合わせてコライダの調整をし、Is Triggerにチェックを入れます。
SearchPlayerスクリプトを作成しSearchAreaに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; public class SearchPlayer : MonoBehaviour { void OnTriggerStay(Collider col) { if (col.tag == "Player") { GetComponentInParent <EnemyScript>().SetTarget(col.transform); } } void OnTriggerExit(Collider col) { if (col.tag == "Player") { GetComponentInParent <EnemyScript>().ResetTarget(); } } } |
プレイヤーキャラクターにはPlayerタグを設定し、検知エリアにプレイヤーキャラクターがいたら、自身の親要素に設定しているEnemyScriptのSetTargetメソッドを呼び出し、追いかけるターゲットを設定します。
プレイヤーキャラクターが検知エリアを出て行った時はResetTargetを呼び出し、ターゲットをなしにします。
EnemyScriptスクリプトはこの後作成します。
敵操作スクリプトEnemyScriptの作成
プレイヤーキャラクターを検知した時にそのプレイヤーキャラクターをターゲットにし、追いかける処理を作成していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | using UnityEngine; using System.Collections; using UnityEngine.Networking; public class EnemyScript : NetworkBehaviour { private NavMeshAgent agent; private Animator animator; private Transform target; // Use this for initialization void Start () { agent = GetComponent <NavMeshAgent> (); animator = GetComponent <Animator> (); } // Update is called once per frame void Update () { if (target == null) { return; } agent.SetDestination (target.position); if (agent.velocity.magnitude >= 0.1f) { animator.SetFloat ("Speed", agent.velocity.magnitude); } else { animator.SetFloat ("Speed", 0f); } } public void SetTarget(Transform target) { if (!isServer) { return; } this.target = target; if (agent.enabled) { agent.Resume (); } } public void ResetTarget() { if (!isServer) { return; } this.target = null; if (agent.enabled) { agent.Stop (); } animator.SetFloat ("Speed", 0f); } } |
Updateメソッドでtargetが指定されていなければreturnをして処理を実行させません。
targetが指定されていれば、ターゲットの位置をNavMeshAgentのSetDestinationメソッドで指定します。
エージェントの現在の速さをアニメーションパラメータのSpeedの値に入れています。
これでUpdateメソッドが呼ばれる度にプレイヤーキャラクターの位置を更新し、移動速度に応じてアニメーションも変更されるようになります。
SetTargetメソッドはSearchPlayerスクリプトから呼び出しターゲットを設定します。
agent.enabledでエージェントが有効な時だけResumeメソッドでエージェントを動かします。
ResetTargetメソッドはSearchPlayerスクリプトから呼び出し、ターゲットをnullにしています。
agent.enabledでエージェントが有効な時はStopメソッドでエージェントを停止します。
SetTarget、ResetTargetメソッドはそれぞれサーバーである時だけ実行する事にします。
ターゲットを見失ったらアニメーションパラメータのSpeedを0にし、Idle状態へと遷移させます。
敵キャラクターのステータススクリプトを作成
敵キャラクターのステータススクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | using UnityEngine; using System.Collections; using UnityEngine.Networking; using UnityEngine.UI; public class EnemyStatus : NetworkBehaviour { // 攻撃力 [SerializeField] private int attackPower; // 体力 [SyncVar] [SerializeField] private int hp; public int GetAttackPower() { return attackPower; } public void SetHp(int hp) { if (!isServer) { return; } this.hp = hp; if (this.hp <= 0) { Destroy (gameObject); } } public int GetHp() { return hp; } // ダメージ処理 public void TakeDamage(int damage) { SetHp (GetHp () - damage); } } |
プレイヤーキャラクターに設定したUStatusとほとんど同じですね。
UStatusと違うのはhpが0以下になったらDestroyを使ってゲームオブジェクトを削除しているところです。
これで敵キャラクターが出来上がりました。
Enemyインスペクタは下のようになります。
シーンオブジェクトの敵の確認
敵キャラクターはナビゲーション機能で動きますので、プレイヤーキャラクターをすり抜けてしまいます。
そこでプレイヤーキャラクターにNavMeshObstacleを取り付け、プレイヤーキャラクターと当たり判定が出来るようにします。
↑のようにプレイヤーキャラクターにNavMeshObstacleを取り付けたらインスペクタの右上のApplyボタンを押してプレハブに反映させておきます。
ナビゲーションで移動する床のゲームオブジェクトのインスペクタの右上のStaticにチェックを入れます。
Navigationウインドウ(メニュー→Windows→Navigation)でNavMeshの設定をしてBakeボタンを押します。
すると地面に薄い青のNavMeshAgentが動ける場所が作成されました。
それではインターネットマッチで接続し、敵キャラクターの同期と敵を攻撃してHPが0以下になった時にゲームオブジェクトが消えるかどうかを確認してみます。
↑のようになりました。
敵キャラクターをインスタンス化して登場させる
先ほどの敵はあらかじめシーン上に存在していました。
途中から敵キャラクターを登場させたいという事もあると思うので、スクリプトから敵キャラクターをネットワーク上にインスタンス化する方法もやってみましょう。
敵キャラクターは先ほど作った敵キャラクターをAssetsフォルダにドラッグ&ドロップしプレハブ化します。
敵用のSpawnPointの作成
敵が登場出来る場所を作成していきます。
ヒエラルキー上で右クリック→Create Emptyで空のゲームオブジェクトを作成し、名前をEnemySpawnPointsとします。
子要素に空のゲームオブジェクトを作成しEnemySpawnPointに番号を付けた名前にします。
これで敵が登場する場所の作成が終わりました。
↑のような位置に敵の登場位置を作成しました。
敵の数を管理するSpawnEnemyゲームオブジェクトの作成
敵の数を管理するゲームオブジェクトを作成していきます。
ヒエラルキー上で右クリック→Create Emptyを選択、空のゲームオブジェクトを作成して名前をSpawnEnemyとします。
SpawnEnemyゲームオブジェクトにはNetworkIdentityコンポーネントを取り付けServer Onlyにチェックを入れます。
Server Onlyにチェックを入れる事でサーバーだけで動作させる事が出来ます。
SpawnEnemyゲームオブジェクトには新しくSpawnEnemyスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | using UnityEngine; using System.Collections; using UnityEngine.Networking; using System.Runtime.Remoting.Messaging; public class SpawnEnemy : NetworkBehaviour { // 敵を登場させる場所 [SerializeField] private Transform[] spawnPoints; // 登場させる敵のゲームオブジェクト [SerializeField] private GameObject enemy; // 出現させる敵の最大数 [SerializeField] private int maxEnemyNum; // 現在出現している敵の数 [SerializeField] private int enemyNum; public override void OnStartServer () { // サーバースタート時にスポーンポイント分の敵をインスタンス化 for (int i = 0; i < spawnPoints.Length; i++) { InstantiateEnemy (i); } StartCoroutine (CheckEnemyNum()); } void InstantiateEnemy(int i) { var obj = Instantiate (enemy, spawnPoints [i].position, Quaternion.identity) as GameObject; NetworkServer.Spawn (obj); enemyNum++; } IEnumerator CheckEnemyNum() { while(true) { yield return new WaitForSeconds (10f); if(enemyNum < maxEnemyNum) { InstantiateEnemy (Random.Range(0, spawnPoints.Length)); } } } public void SetEnemyNum() { enemyNum--; } } |
spawnPointsには先ほど作った敵を登場させる位置をインスペクタで指定し、enemyには敵のプレハブを指定します。
maxEnemyNumにゲームに登場出来る敵の最大数を指定し、enemyNumは現在出現している敵の数が入ります。
OnStartServerメソッドでspawnPointsの数分の敵をインスタンス化します。
OnStartServerメソッドはNetworkBehaviourのメソッドでサーバーがアクティブになった時に呼び出されます。
これをオーバーライドして記述します。
コルーチンで敵の数を監視し、敵が最大数より少なくなっていたら新しい敵をインスタンス化します。
InstantiateEnemyメソッドが実際に敵をインスタンス化している処理です。
インスタンス化した敵をクライアントでも登場させる為、NetworkServer.Spawnでクライアントに敵を登場させています。
CheckEnemyNumメソッドでは10秒毎に敵の数をチェックし、少なければインスタンス化しています。
SetEnemyNumメソッドは敵を倒した時にenemyNumの数を減らす処理をしていて、EnemyStatusから呼び出します。
EnemyStatusのフィールドとSetHpメソッドに変更を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [SerializeField] private SpawnEnemy spawnEnemy; void Start() { spawnEnemy = FindObjectOfType (typeof(SpawnEnemy)) as SpawnEnemy; } public void SetHp(int hp) { if (!isServer) { return; } this.hp = hp; if (this.hp <= 0) { spawnEnemy.SetEnemyNum (); Destroy (gameObject); } } |
StartメソッドでFindObjectOfTypeでSpawnEnemyスクリプトを取得します。
敵のHPが0以下になった時にSpawnEnemyスクリプトのSetEnemyNumメソッドを呼び出し、敵の数を減らします。
NetworkManagerに敵のプレハブを登録
ゲームの途中からインスタンス化するゲームオブジェクトはあらかじめNetworkManagerのSpawn InfoのRegistered Spwanable Prefabsに登録しておきます。
↑のように敵のプレハブを設定します。
敵のインスタンス化の確認をする
これでゲームの途中から敵のプレハブをインスタンス化する事が出来るようになりました。
いくつかアプリケーションを実行し、確認してみましょう。
↑のようになりました。
敵が無限に出てくるのがわかりますね。(^_^)v
今回の仕様では敵の登場位置はランダムに決定しているので、同じ場所敵が登場してしまいます。
SpawnPoint毎に敵の数を管理し、そこから出現した敵が倒された時にその場所から敵を生成するようにするとより本格的な感じになりますね。