PONOS Advent Calendar 2023 23日目の記事です。
昨日は@ANIZA_15さんの「LocoをCloud Runで実行する」でした。
はじめに
ゲーム制作をしていると、「一人で遊ぶゲームだけじゃなくてオンラインで遊ぶゲームを作ってみたい!」と誰しも一度くらいは考えたことがあるのではないでしょうか。しかし、実際のところは「ネットワークに関する知識がないから作れない...」と制作に踏み切れずにいた人は多いと思います。
私自身、一人で遊ぶゲームはある程度作れるものの、ネットワークに関する知識はまだまだで、マルチプレイのゲームの制作にいまいち踏み切れていませんでした。
そんな時に見つけたのが「PUN2」というネットワークエンジンです。
今回はそんな私がPUN2を用いてマルチプレイを今更ながら実装してみたというお話です。
PUN2とは
そもそも「PUN2とはなんぞや」ということですが、正式名称を「Photon Unity Networking」といい、上記にもあるとおりマルチプレイゲームを制作することができるネットワークエンジンのことです。
搭載されているものとしてサーバー、クライアントSDKがあり、一通りマルチプレイゲームを実装するのに必要なものが揃っています。
実装物
今回サンプルとして実装したものはこんな感じのものです。
矢印のついたプレイヤーの操作ができて、右下のInputFieldに入力したメッセージを相手に送信できるといったとてもシンプルなものです。
開発環境
Unity : 2019 4.37f1
前準備
公式サイト:PUN2
PUN2を使用したゲームを作るにはPUN2のアカウントが必要なので公式サイトのリンクからアカウントを作成します。
アカウントが作成できたら以下の手順で前準備を進めていきます。
1.アプリの作成
基本的には画像のように設定すれば大丈夫です。アプリケーション名は制作するアプリの名前を入れてください。今回はPUN2Sampleとしています。
アプリの作成が完了するとアプリケーションIDというものが発行されるのでこちらを控えておきます。
2.Unityにパッケージを導入
続いて、Unityに次のパッケージを導入します。
PUN 2 - FREE
インポートすると下記のウィンドウが表示されるので控えておいたアプリケーションIDをAppId or Email
の項目に入力します。(アカウントに関連する項目なので念のため隠しています。)
3.Unity側の設定
セットアップが完了するとPhotonServerSettings
というファイルが生成されるので、そのファイルを下記の画像のように設定します。
以上で前準備は完了です。これで制作を進めていく環境が整いました。
プレイヤーの実装
PUN2では同期させるオブジェクトのことを「ネットワークオブジェクト」といいます。
今回はプレイヤーの動きを同期させたいのでプレイヤーをネットワークオブジェクトとして実装していきます。
1.プレイヤーとなるオブジェクトを作成
Spriteを新規作成し、PhotonView
、PhotonTransformView
という二つのコンポーネントをアタッチします。そしてPhotonTransformView
コンポーネントのSyncronizeOptions
で同期させたいTransformの値を設定します。今回は位置と回転の値を同期させたかったので以下のようになりました。
2.作成したオブジェクトを操作するスクリプトの作成
プレイヤーオブジェクトが作成できたら操作するためのスクリプトを作成します。
ソースコードは以下です。(一部抜粋)
using UnityEngine;
using Photon.Pun;
public class PlayerSample : MonoBehaviourPun
{
[SerializeField] GameObject allow = default;
void Start()
{
if(photonView.IsMine)
{
allow.gameObject.SetActive(true);
}
}
void Update()
{
if (!photonView.IsMine) return;
// ここから移動処理
// キーの入力によってTransformの値を計算する処理を入れていますが、長いので割愛
}
まず、Photon.Pun
をusingし、継承するクラスをMonoBehaviourPun
に変更しています。
これがないとPhotonEngineの機能を使用することができません。
続いてphotonView.IsMine
というプロパティですが、このプロパティが所有者が自分かどうかを保持しており、if (PhotonView.IsMine)
で所有者が自分の場合にのみ処理を行なうといったことをしています。
if (PhotonView.IsMine)
の記述がないとどのネットワークオブジェクトも操作できてしまう...といったことが発生してしまうので、ネットワークオブジェクトとして実装する以上、この記述は必要です。
あとは先ほど作成したプレイヤーオブジェクトにこのスクリプトをアタッチし、Resourcesフォルダに入れてPrefab化します。(後述する手順の場合、Resourcesに入れないと生成することができないので注意)
ここまでできたらプレイヤーオブジェクトの作成は完了です。
マルチプレイの実装
PUN2のマルチプレイの仕組みはまず、サーバー(ロビー)に接続しその後にそれぞれの部屋(ルーム)に入室して同じルームの人と遊ぶといった仕組みになっているようです。ここではサーバーに接続後、それぞれのルームに入室し先ほど作成したプレイヤーを生成するというところまで実装していきます。
サーバーに接続、部屋に入室するマネージャークラスを実装する
実際にクライアントがゲーム起動後にサーバーに接続し、部屋に入室する処理を実装していきます。
ソースコードは以下です。
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class NetworkManagerSample : MonoBehaviourPunCallbacks
{
void Start()
{
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster()
{
PhotonNetwork.JoinRandomRoom();
}
public override void OnJoinRandomFailed(short returnCode, string message)
{
var option = new RoomOptions();
option.MaxPlayers = 2;
PhotonNetwork.CreateRoom(null,option);
}
public override void OnJoinedRoom()
{
Vector3 pos = new Vector3(Random.Range(-3.0f, 3.0f),Random.Range( -3.0f, 3.0f), 0.0f);
GameObject player = PhotonNetwork.Instantiate("Player", pos, Quaternion.identity);
}
}
まず、Photon.Pun
とPhoton.RealTime
をusingし、継承MonoBehaviourPunCallBacks
に変更しています。プレイヤースクリプト同様、これをしないとPUN2の機能を使用することができません。
ここからはPUN2が提供している関数の説明です。
PhotonNetwork.ConnectUsingSettings()
サーバーへの接続を試みる関数です。
OnConnectedToMaster()
サーバーに接続できた時に呼ばれるコールバックです。
ここではPhotonNetwork.JoinRandomRoom()
関数でランダムな部屋に入室するようにしています。
OnJoinRandomFailed(short returnCode, string message)
ランダムな部屋に入室できなかった時に呼ばれるコールバックです。
ここではvar option = new RoomOptions()
で部屋の詳細を作成・設定し、作成した詳細の部屋を新規作成するようにしています。(今回は部屋の最大人数を2人に設定しています。)
OnJoinedRoom()
部屋に入室できた時に呼ばれるコールバックです。
ここでは先ほど作成したプレイヤーオブジェクトをランダムな位置に作成するようにしています。
生成するときはPhotonNetwork.Instantiate()
を使用して生成します。
あとはこのスクリプトを空のオブジェクトにアタッチしてシーン上に配置すればマルチプレイの実装は完了です。
メッセージを送信する
マルチプレイの実装までで実際に操作ができオブジェクトの同期をとることはできているのではないかと思います。
ここではさらにプラスαの要素として相手にメッセージを送信・受信する部分を実装していきます。
メッセージを入力・表示・送受信するクラスを実装する
InputFieldに入力されたメッセージを相手に送信する処理を実装していきます。
ソースコードは以下です。
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class MessageManagerSample : MonoBehaviourPun
{
[SerializeField] InputField inputField = default;
[SerializeField] Text receiveText = default;
void Start()
{
receiveText.text = "";
}
void SetReceiveText(string message)
{
receiveText.text = message;
StartCoroutine(nameof(HideAfterSeconds), 3.0f);
}
IEnumerator HideMessageAfterSeconds(float time)
{
yield return new WaitForSeconds(time);
receiveText.text = "";
}
public void SendText(string message)
{
photonView.RPC(nameof(ReceiveText), RpcTarget.Others, message);
}
[PunRPC]
void ReceiveText(string message)
{
SetReceiveText(message);
}
まず、Photon.Pun
とPhoton.Realtime
をusingし、継承するクラスをMonoBehaviourPun
に変更しています。
ここからはそれぞれの関数の説明です。
SetReceiveText(string message)
引数の文字列をrecceiveTextに設定する関数です。設定と同時にHideMessageAfterSecond()
というコルーチンを呼び出しています。
HideMessageAfterSecond(float time)
指定した秒数後にreceiveTextに設定されている文字列を空にするコルーチンです。
SendText(string message)
相手にメッセージを送信する関数です。
photonView.RPC()
という関数を用いて相手にメッセージを送信します。
この関数の引数ですが、第一引数に受信時に実行する関数、第二引数に実行対象、第三引数に第一引数で指定した関数の引数と同じ型の値を指定します。
RPCの実行対象には以下の種類があるようです。
RPCの実行対象 | 実行者自身 | 受信者 | 途中参加者 |
---|---|---|---|
RpcTarget.All | 即座に実行される | 通信を介して実行される | 実行されない |
RpcTarget.Others | 実行されない | 通信を介して実行される | 実行されない |
RpcTarget.AllBuffered | 即座に実行される | 通信を介して実行される | 実行される |
RpcTarget.OthersBuffered | 実行されない | 通信を介して実行される | 実行される |
RpcTarget.AllViaServer | 通信を介して実行される | 通信を介して実行される | 実行されない |
RpcTarget.AllBufferedViaServer | 通信を介して実行される | 通信を介して実行される | 実行される |
これを使い分けることで特定のプレイヤーのみにメッセージを送信するといったこともできるのではないかと思います。
ReceiveText(string message)
受信時に実行される関数です。
この関数の中でSetReceiveText()
を実行しています。
受信時に実行する関数には[PunRPC]
という属性をつける必要があります。
あとはシーン上にInputField、Textオブジェクトを配置してこのスクリプトを空のオブジェクトにアタッチ、それぞれの参照を設定、InputFieldのOnEndEdit
の項目にSendText()
を登録します。最後に今回作成したMessageManagerSample.cs
ですが、photonView
に実装されている関数を使用しているので空のオブジェクトにはPhotonViewコンポーネントもアタッチしないといけません。
ここまでできたらメッセージを送信・受信する処理の実装は完了です。
最後に
今回このサンプルを実装するのに使用したPUN2の機能はほんの一部分だけではないかと思います。
この機能だけでも時間をかければネットワークの知識がなくても割と本格的なマルチプレイゲームが作れると思いますので一度触ってみてはいかがでしょうか。
ここまでご覧いただきありがとうございました。次回はbluenova1221さんの「UnityエンジニアがGodot Engineでゲームを作ってみた」です。