Assets/Resources フォルダを開き、 NPC.prefab の右端にある「>」のマークをクリックします。
すると、NPC.prefab の中身が展開されます。
展開されたデータのうち、「Bear」と書かれたオブジェクトを選択します。
なお、当然のことながら、「クライアントに任せる=ある程度のタイムラグを許容した作りにする」必要があります。 その手法については、ゲームシステムの調整によって異なる話ですので、今回は割愛します。
Assets/Resources フォルダを開き、 NPC.prefab の右端にある「>」のマークをクリックします。
すると、NPC.prefab の中身が展開されます。
展開されたデータのうち、「Bear」と書かれたオブジェクトを選択します。
まず Assets/Resources フォルダーをクリックして選択し、その中にある NPC.prefab を選択します。
NPC の Inspector にある「Open Prefab」のボタンを押します。
すると Hierarchy の表示箇所に NPC.prefab がリスト化されて表示されますので、
親オブジェクト「NPC」の左隣にある三角マークをクリックし、子オブジェクトの「Bear」を選択します。
では実際に変更を加えましょう。
GameObject[] m_Player = null; Animator[] m_PlayerAnimator = null; int selectedPlayer = 0; Vector3 selectedPos = Vector3.zero;
複数のプレイヤーに対応するために、プレイヤー参照変数を配列式にします。
void Start () { m_Animator = GetComponent<Animator>(); m_Animator.logWarnings = false; // so we dont get warning when updating controller in live link ( undocumented/unsupported function!) m_Player = GameObject.FindGameObjectsWithTag("Player"); m_PlayerAnimator = new Animator[m_Player.Length]; for( int i = 0; i < m_Player.Length; i++ ) { m_PlayerAnimator[i] = m_Player[i].GetComponent<Animator>(); } }
複数プレイヤーに対応できるように「Player」タグの追加キャラクタを複数取得し、そのAnimator情報を取得します。
bool ShouldShootPlayer() { // ルーム入室中のみ、プレイヤー情報の更新を行なう if(MonobitEngine.MonobitNetwork.isConnect && MonobitEngine.MonobitNetwork.inRoom) { if(m_Player.Length != MonobitEngine.MonobitNetwork.room.playerCount) { // プレイヤー情報の再取得 m_Player = GameObject.FindGameObjectsWithTag("Player"); m_PlayerAnimator = new Animator[m_Player.Length]; for (int i = 0; i < m_Player.Length; i++) { m_PlayerAnimator[i] = m_Player[i].GetComponent<Animator>(); } } } for (int i = 0; i < m_Player.Length; i++) { float distanceToPlayer = Vector3.Distance(m_Player[i].transform.position, transform.position); if (distanceToPlayer < m_AttackDistance) { AnimatorStateInfo info = m_PlayerAnimator[i].GetCurrentAnimatorStateInfo(0); // real bears don't shoot at dead player! if (!info.IsName("Base Layer.Dying") && !info.IsName("Base Layer.Death") && !info.IsName("Base Layer.Reviving")) { if(selectedPlayer < 0 ) { selectedPlayer = i; selectedPos = m_Player[i].transform.position; } return true; } } } return false; }
void OnAnimatorMove() { if(CheatRoot) { if(!enabled || !GetComponent<CharacterController>().enabled) return; // Cheat the root to align to player target. if(m_Animator.GetBool("Shoot")) { if (selectedPlayer >= 0) { m_LookAtPosition.Set(selectedPos.x, transform.position.y, selectedPos.z); // Kill Y. } transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(m_LookAtPosition-transform.position), Time.deltaTime * 5); m_Animator.rootRotation = transform.rotation; } GetComponent<CharacterController>().Move(m_Animator.deltaPosition); transform.rotation = m_Animator.rootRotation; ForceOnFloor(); } }
void SpawnBullet() { GameObject newBullet = Instantiate(Bullet, BulletSpawnPoint.position , Quaternion.Euler(0, 0, 0)) as GameObject; Destroy(newBullet, m_BulletDuration); Vector3 direction = selectedPos - BulletSpawnPoint.position; direction.y = 0; newBullet.GetComponent<Rigidbody>().velocity = Vector3.Normalize(direction)* m_BulletSpeed; if(BulletParent)newBullet.transform.parent = BulletParent; m_HasShootInCycle = true; }
public class NPC_ShootPlayer : MonobitEngine.MonoBehaviour {
RPCメッセージを受信するためには、最低限 MonobitEngine.MonoBehaviour を継承している必要があります。
// MonobitView コンポーネント MonobitEngine.MonobitView m_MonobitView = null;
前述までと同様、MonobitView コンポーネント本体の参照のための変数を用意します。
void Awake() { // すべての親オブジェクトに対して MonobitView コンポーネントを検索する if (GetComponentInParent<MonobitEngine.MonobitView>() != null) { m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>(); } // 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null) { m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>(); } // 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する else { m_MonobitView = GetComponent<MonobitEngine.MonobitView>(); } }
ここも前述までにも説明した通り、親オブジェクトに存在する MonobitView を検索し、
//SpawnBullet(); m_MonobitView.RPC("SpawnBullet", MonobitEngine.MonobitTargets.All, selectedPos);
弾を投げる瞬間に、SpawnBullet() の実行を「ルーム内のプレイヤー全員」に対して実行するようにします。
// Spawns bullet [MunRPC] void SpawnBullet(Vector3 targetPos) { GameObject newBullet = Instantiate(Bullet, BulletSpawnPoint.position, Quaternion.Euler(0, 0, 0)) as GameObject; Destroy(newBullet, m_BulletDuration); Vector3 direction = targetPos - BulletSpawnPoint.position; direction.y = 0; newBullet.GetComponent<Rigidbody>().velocity = Vector3.Normalize(direction) * m_BulletSpeed; if (BulletParent) newBullet.transform.parent = BulletParent; m_HasShootInCycle = true; selectedPlayer = -1; }
MonobitView.RPC() メソッドを使ったRPCメッセージの送信に対し、弾を投げる処理 SpawnBullet の「受信関数化」を行います。
Assets/Resources フォルダを開き、 Player.prefab の右端にある「>」のマークをクリックします。
すると、player.prefab の中身が展開されます。
展開されたデータのうち、「Dude」と書かれたオブジェクトを選択します。
まず Assets/Resources フォルダーをクリックして選択し、その中にある Player.prefab を選択します。
Player の Inspector にある「Open Prefab」のボタンを押します。
すると Hierarchy の表示箇所に Player.prefab がリスト化されて表示されますので、
親オブジェクト「Player」の左隣にある三角マークをクリックし、子オブジェクトの「Dude」を選択します。
public class NPC_ShootPlayer : MonobitEngine.MonoBehaviour {
このコードと同じ処理を、「RPC 受信関数」としてまとめます。
[MunRPC] void SpawnBazookaBullet() { GameObject newBullet = Instantiate(Bullet, BulletSpawnPoint.position, Quaternion.Euler(0, 0, 0)) as GameObject; Destroy(newBullet, m_BulletDuration); newBullet.GetComponent<Rigidbody>().velocity = -BulletSpawnPoint.forward * m_BulletSpeed; newBullet.GetComponent<DamageProvider>().SetScaleBullet(); newBullet.SetActive(true); if (BulletParent) newBullet.transform.parent = BulletParent; }
m_MonobitView.RPC("SpawnBazookaBullet", MonobitEngine.MonobitTargets.All, null);
public class LookAhead : MonobitEngine.MonoBehaviour {
何度も繰り返しになりますが、RPCメッセージを受信するためには、最低限 MonobitEngine.MonoBehaviour を継承している必要があります。
// MonobitView コンポーネント MonobitEngine.MonobitView m_MonobitView = null;
前述までと同様、MonobitView コンポーネント本体の参照のための変数を用意します。
void Awake() { // すべての親オブジェクトに対して MonobitView コンポーネントを検索する if (GetComponentInParent<MonobitEngine.MonobitView>() != null) { m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>(); } // 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null) { m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>(); } // 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する else { m_MonobitView = GetComponent<MonobitEngine.MonobitView>(); } }
ここも前述までにも説明した通り、親オブジェクトに存在する MonobitView を検索し、
Vector3 lookAheadPosition;
弾の発射方向を設定する変数 lookAheadPosition を用意します。
lookAheadPosition = HeadTransform.position + (HeadTransform.forward * 10);
Inspectorで登録された HeadTransform(プレイヤーキャラクタの頭のスケルトン)の位置・方向に合わせて初期値を設定します。
[MunRPC] void UpdateLookAhead(Vector3 position) { lookAheadPosition = position; }
void OnAnimatorIK(int layerIndex) { if(!enabled) return; if(layerIndex == 0) // do IK pass on base layer only { if (m_MonobitView.isMine) { float vertical = m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Locomotion.Idle") ? 10 : 0; Vector3 lookAheadPosition = HeadTransform.position + (HeadTransform.forward * 10) + (HeadTransform.up * vertical * Input.GetAxis("Vertical")) + (HeadTransform.right * 20 * Input.GetAxis("Horizontal")); m_Animator.SetLookAtPosition(lookAheadPosition); m_Animator.SetLookAtWeight(1.0f, 0.1f, 0.9f, 1.0f, 0.7f); m_MonobitView.RPC("UpdateLookAhead", MonobitEngine.MonobitTargets.All, lookAheadPosition); } else { m_Animator.SetLookAtPosition(lookAheadPosition); m_Animator.SetLookAtWeight(1.0f, 0.1f, 0.9f, 1.0f, 0.7f); } } }
ここでは、プレイヤー自身の操作( MonobitView.isMine == true )の場合、既存の lookAheadPosition を使った頭位置(弾の発射方向)を変更し、