MLAPI その9
非推奨となった UNet のやつを参考に、オブジェクトプールをやってみる。
docs.unity3d.com
mlapi.network
mlapi.network
シーン内に SpawnManager オブジェクトを作ってスクリプトをアタッチ。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SpawnManager : MonoBehaviour { public GameObject bullet; private GameObject[] bulletPool; private int poolSize = 5; void Start() { bulletPool = new GameObject[poolSize]; for (int i = 0; i < poolSize; i++) { bulletPool[i] = (GameObject)Instantiate(bullet, Vector3.zero, Quaternion.identity); bulletPool[i].name = "PoolObject" + i; bulletPool[i].SetActive(false); } MLAPI.Spawning.SpawnManager.RegisterSpawnHandler(MLAPI.Spawning.SpawnManager.GetPrefabHashFromIndex(1), SpawnObject); MLAPI.Spawning.SpawnManager.RegisterCustomDestroyHandler(MLAPI.Spawning.SpawnManager.GetPrefabHashFromIndex(1), UnSpawnObject); } public GameObject GetFromPool(Vector3 position, Quaternion rotation) { for (int i = 0; i < poolSize; i++) { if (!bulletPool[i].activeSelf) { Debug.Log("Activating GameObject " + bulletPool[i].name + " at " + position); bulletPool[i].transform.position = position; bulletPool[i].transform.rotation = rotation; bulletPool[i].SetActive(true); return bulletPool[i]; } } Debug.LogError("Could not grab GameObject from pool, nothing available"); return null; } public MLAPI.NetworkedObject SpawnObject(Vector3 position, Quaternion rotation) { return GetFromPool(position, rotation).GetComponent<MLAPI.NetworkedObject>(); } public void UnSpawnObject(MLAPI.NetworkedObject networkedObject) { Debug.Log("Re-pooling GameObject " + networkedObject.gameObject.name); networkedObject.gameObject.SetActive(false); } }
PlayerCubeController の方で、SpawnManager 経由で弾を呼び出すように変更。
public class PlayerCubeController : MLAPI.NetworkedBehaviour { private SpawnManager spawnManager; private void Start() { spawnManager = GameObject.Find("SpawnManager").GetComponent<SpawnManager>(); } [MLAPI.Messaging.ServerRPC] private void Shoot() { GameObject bullet = spawnManager.GetFromPool(new Vector3(Random.Range(-9, 9), 1.0f, Random.Range(-9, 9)), Quaternion.Euler(0, 0, 90)); bullet.GetComponent<MLAPI.NetworkedObject>().Spawn(); StartCoroutine(BackToPool(bullet, 2.0f)); } private IEnumerator BackToPool(GameObject bullet, float timer) { yield return new WaitForSeconds(timer); MLAPI.NetworkedObject no = bullet.GetComponent<MLAPI.NetworkedObject>(); spawnManager.UnSpawnObject(no); no.UnSpawn(); } }
弾が自動的に消えないようにコメントアウト。
public class Bullet : MonoBehaviour { void Update() { //restTime -= Time.deltaTime; //if (restTime < 0) //{ // Destroy(this.gameObject); //} } }
いまひとつ理解ができていない……。
これだとクライアントが複数つながっても、シーン内に弾が 5 発しか存在できない。
なかなか難しかった。
MLAPI その8
弾が出現したら、前方に直線上に進むようにする。
Bullet スクリプトを作って Bullet にアタッチ。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Bullet : MonoBehaviour { private float speed = 500; void Start() { GetComponent<Rigidbody>().AddForce(transform.up * speed); } }
弾らしく動かすために、transform.forward ではなくて、transform.up にした。
Quaternion.Euler(0, 0, 90) で出現させているので、世界では横に飛んでいく。
このままだと飛び続けて消えないので、3 秒経ったら消えるようにしたい。
private float restTime = 3; void Update() { restTime -= Time.deltaTime; if (restTime < 0) { Destroy(this.gameObject); } }
弾が消えるのは弾の機能だから、RPC が絡んでこない。それぞれのクライアントで自動的に消える。
弾が何かと接触したら、それも消えるようにしたい。
OnCollisionEnter のコールバックで設定する。
void OnCollisionEnter(Collision other) { Destroy(this.gameObject); }
何回かスペースキーを連打して PlayerCube に当たると、その時点で消えるようになった。
しかし、オブジェクトの生成と消滅はコストが比較的高くて、こういうやつだとよくオブジェクトプールの手法が使われるらしいので次はそれをやってみる。
MLAPI その7
スペースキーを押したら弾がでるようにしたい。
Capsule をシーン内に入れて、Bullet という名前にする。
最初はネットワーク同期をやってみたいので、NetworkedObject をアタッチ。
適当にスケールを小さくして、Prefab にして、シーン内から削除。
NetworkingManager の NetworkedPrefabs の「+」から Bullet を追加する。
ここの Prefab は、MLAPI.NetworkingManager.Singleton.NetworkConfig.NetworkedPrefabs で取れるらしい。
スペースキーを押したときに、弾が出現するようにしてみる。
public class PlayerCubeController : MLAPI.NetworkedBehaviour { private void Update() { if (!IsOwner) { return; } // 移動のやつ // スペースキーを押して弾出現 if (Input.GetKeyDown(KeyCode.Space)) { Shoot(); } } private void Shoot() { GameObject prefab = MLAPI.NetworkingManager.Singleton.NetworkConfig.NetworkedPrefabs[1].Prefab; GameObject go = Instantiate(prefab, new Vector3(Random.Range(-9, 9), 1.0f, Random.Range(-9, 9)), Quaternion.Euler(0, 0, 90)); go.GetComponent<MLAPI.NetworkedObject>().Spawn(); }
ホストだけで確認するとこれでよかったが、クライアントと一緒に試すとエラーがでる。
ArgumentException: An item with the same key has already been added. Key: 1
これはホストとクライアントで同じことをしようとしてエラーが起こっているらしい。
たぶんやり方は 2 つあって、サーバに実行を集約させてクライアントは享受するだけにするか、自クライアントで実行する+それ以外のクライアントに実行させるか。
とりあえず前者で、サーバ RPC で実行する。
if (Input.GetKeyDown(KeyCode.Space)) { InvokeServerRpc(Shoot); } } [MLAPI.Messaging.ServerRPC] private void Shoot() { // 略 }
たぶんこのやり方だとスペースキーを押してから、サーバへ通信を往復して実行してる分、若干ラグがあるのかな? もし完全に同期を求められないようなやつなら、後者の自クライアントで実行しつつ、サーバを介して他のクライアントで実行が良さそうかなと思った。