Unityの公式チュートリアル「マルチプレイヤーネットワーキング」の実習の模様をお送りしております。前回の記事はこちらからどうぞ
生成と再生成
公式サイトのチュートリアルページ 生成と再生成
今回でいよいよ最後のステップ。プレイヤーの生成位置を設定します。
プレイヤーの初期生成位置を作成
- 空のゲームオブジェクトを 1 つ新規作成して名前を “Spawn Position 1” に変更
- オブジェクト Spawn Position 1 を選択
- Network > NetworkStartPosition コンポーネントを追加
- Transform Position を X 3, Y 0, Z 0 に設定
- オブジェクト Spawn Position 1 を複製する
- 複製したオブジェクトの名前を “Spawn Position 2” に変更
- Spawn Position 2 ゲームオブジェクトを選択
- Transform Position を X -3, Y 0, Z 0 に設定
- [Hierarchy]ウィンドウ > Network Managerを選択
- Network Managerコンポーネント > Spawn Info のドロップダウンメニューを開く
- Player Spawn Method を Round Robin に変更
オブジェクト Spawn Position 1 を[Inspector]ウィンドウで確認するとこのようになっています。
オブジェクト Spawn Position 2 を[Inspector]ウィンドウで確認するとこのようになっています。
オブジェクト Network Manager の[Inspector]ウィンドウで Network Manager コンポーネント を確認するとこのようになっています。
動作確認
- シーンをスタンドアロンのアプリケーションとして Build and Run を実行
- ゲーム内 UI から[Host]ボタンをクリックしてゲームをホストとして開始
- プレイヤーゲームオブジェクトを動かす
- Unity に戻ってPlay モードを開始
- ゲーム内 UI から[LAN Client]ボタンをクリックしてクライアントとしてホストに接続
- ゲームが開始されると、シーン内の異なる場所にプレイヤーが生成されることを確認
- スタンドアロン・プレイヤーを終了
- Unity に戻ってPlay モードを終了
ビルドしたゲームを実行し、ゲームを再生して[Scene]ビューで上から見下ろして確認するとホスト側のプレイヤーとクライアント側のキャラクターがそれぞれオブジェクトSpawn Position の位置に生成されています。
Health スクリプトを修正して再生成位置を設定する
- Health スクリプトを開く
- 生成位置を格納する配列 NetworkStartPosition を追加
- Start 関数を追加
- NetworkStartPosition コンポーネントの全てのインスタンスを検索してそれを Spawn Points 配列に保存するロジックを追加
- RpcRespawn 関数内の isLocalPlayer チェック内にある既存のコードを削除
- RpcRespawn 関数内の、isLocalPlayer チェック内のコードを、生成位置(spawn points)の配列から抽出したランダムな位置を使用するように更新
- スクリプトを保存
修正したスクリプトのコードにコメントを加えたものがこちら。
using System.Collections; using System.Collections.Generic; using UnityEngine; // UIコンポーネント関数を使用するため名前空間の追加 using UnityEngine.UI; // ネットワークコンポーネント関数を使用するため名前空間の追加 using UnityEngine.Networking; public class Health : NetworkBehaviour { // 体力値を格納する変数(定数値 100) public const int maxHealth = 100; // キャラクター破壊のフラグ変数 public bool destroyOnDeath; // 現在の体力値を格納する変数(ネットワーク同期、初期値は maxHealth) // 変数値が変更されたら関数 OnChangeHealth を実行 // OnChangeHealth の引数 health に currentHealth の値を渡す [SyncVar(hook = "OnChangeHealth")] public int currentHealth = maxHealth; // RectTranform コンポーネントを格納する変数 public RectTransform healthBar; // キャラクター生成場所を格納する配列 private NetworkStartPosition[] spawnPoints; // ゲームの初期化 void Start() { // プレイヤーが自分自身の場合 if (isLocalPlayer) { // 配列 spawnPoints に NetworkStartPosition コンポーネントを検索して格納 spawnPoints = FindObjectsOfType<NetworkStartPosition>(); } } // ダメージ処理(サーバー上のみ) public void TakeDamage(int amount) { // サーバーでない場合 if (!isServer) { // 以降の処理を中断 return; } // 現在の体力値から 引数 amount の値を引く currentHealth -= amount; // 現在の体力値が 0 以下の場合 if (currentHealth <= 0) { // 破壊フラグが True の場合 if (destroyOnDeath) { //オブジェクトを破壊 Destroy(gameObject); } // 条件に当てはまらない場合 else { // 体力値をリセット(体力最大値を現在値に渡す) currentHealth = maxHealth; // プレイヤーをリセットする処理を実行 //(サーバーで呼び出されクライアントで実行) RpcRespawn(); } } } // 体力値ゲージの変更処理 //(引数 health には 変数 currentHealth が渡される ) void OnChangeHealth(int health) { // RectTranform コンポーネントのサイズを体力値に合わせて変更 //(X値に health 値を代入、Y値は RectTranform コンポーネントのY値を代入) healthBar.sizeDelta = new Vector2(health, healthBar.sizeDelta.y); } // サーバーで呼び出されクライアントで実行する属性 [ClientRpc] // プレイヤーをリセットする処理(Rpc プレフィックス) void RpcRespawn() { // プレイヤーが自分自身の場合 if (isLocalPlayer) { // オブジェクト初期生成位置として変数 spawnPoint に 0 を代入 Vector3 spawnPoint = Vector3.zero; // 配列 spawnPoints が空でなく、0より大きい場合 if (spawnPoints != null && spawnPoints.Length > 0) { // 配列 spawnPoints からランダムに 1 つ抽出したコンポーネントの // transform.position の値を spawnPoint に渡す spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)].transform.position; } // プレイヤーの Position 値に spawnPoint の値を渡す transform.position = spawnPoint; } } }
動作確認
- シーンをスタンドアロンのアプリケーションとして Build and Run を実行
- ゲーム内 UI から[Host]ボタンをクリックしてゲームをホストとして開始
- プレイヤーゲームオブジェクトを動かす
- Unity に戻ってPlay モードを開始
- ゲーム内 UI から[LAN Client]ボタンをクリックしてクライアントとしてホストに接続
- ゲームの開始時に、シーン内の異なる位置にプレイヤーが生成されることを確認
- 体力がゼロになったプレイヤーは生成位置からランダムに再生成されることを確認
- スタンドアロン・プレイヤーを終了
- Unity に戻ってPlay モードを終了
このステップを終えてゲームを実行。ビルドしたゲームを2つ実行してウィンドウを並べました。画面右側がホスト(サーバー&クライアント)で画面左がクライアントです。左側奥にいるクライアントのプレイヤーを攻撃して体力が無くなると…
再生成位置はランダムに変わります。なので同じ Spawn Position で連続して再生成される場合もあります。
今回のステップは再生成位置のオブジェクトを配列に格納してランダムに取り出す部分が難しかったですね。今回のステップでこのチュートリアルは終了ですが、引き続き改造を進めたいと思います。