このチュートリアルでできること
前回は、VRoidアバターをUnityに取り込み、キーボード操作で動かせるようにしました。
今回はその続きとして、Photonを利用して複数プレイヤーが同時に接続できるように設定していきます。
・Photonを使ったマルチプレイ接続
・アバター生成とカメラ追従
・他プレイヤーとのアニメーション同期
・頭上の名前表示の同期
必要な素材とツール
ツール | 概要 |
---|---|
Photon PUN2 | Unity Asset Store で無料配布:https://assetstore.unity.com/packages/tools/network/pun-2-free-119922 |
Photon App ID | https://dashboard.photonengine.com/ でアカウント作成後、App ID を取得 |
Photonの設定
Photonのアカウント作成とApp ID取得
- Photon Cloud にアクセスし、アカウントを作成してサインイン
- ダッシュボード画面を開く:https://dashboard.photonengine.com/
- 以下の手順でアプリケーションを作成
CREATE A NEW APP
をクリックMultiplayer Game
を選択- Select Photon SDK →
Pun
を選択 - Application Name → 任意の名前を入力(例:Photon_Test)
CREATE
をクリック
- 作成されたアプリケーションの
App ID
をコピー



PUN2のインポート
- Unityのアセットストアから PUN2 - FREE をインポート
- Unityに戻り、
Package Manager > My Assets > PUN2 - FREE
からImport
PUN Setup
画面が表示されたら、AppId or Email
に先ほどコピーしたApp IDをペーストSetup Project
をクリックし、完了後にClose




サーバーの設定
Project > Assets > Photon > PhotonUnityNetworking > Resources > PhotonServerSettings
を開くInspector
>Server / Cloud Settings
のFixed Region
をjp
に設定(日本サーバ優先)

VRoidアバターの準備
VRoid Studio でのアバター準備
- VRoid Studio から、マルチプレイ用にもう1体のアバターをダウンロード
- Unity の
Assets > Avatar
フォルダにドラッグ&ドロップ - Hierarchy にアバターを配置し、Animator Controller に前回作成した
WalkingAnimator
を設定


スクリプトの実装
スクリプトの追加
Assets > Scripts
内で右クリック
→Create > MonoBehaviour Script
を選択
→MultiplayerManager
,AvatarAnimationSync
,AvatarNameLabel
を新規追加- それぞれの
.cs
ファイルをダブルクリックし、スクリプトをコピーして保存AvatarMovementController
,AvatarCameraFollow
の内容をアップデート(プログラムの解説はコメントに記載)
スクリプト名 | 役割 |
---|---|
MultiplayerManager | Photon接続とアバターの生成 |
AvatarAnimationSync | 歩行状態の同期 |
AvatarNameLabel | 頭上にプレイヤー名を表示 |
AvatarMovementController | アバターの移動制御 |
AvatarCameraFollow | 自分のアバターにカメラを追従 |

MultiplayerManager.cs
Photon接続とアバターの生成
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
// Manages multiplayer connections and avatar instantiation
// マルチプレイヤー接続とアバター生成を管理する
public class MultiplayerManager : MonoBehaviourPunCallbacks
{
// List of avatar prefabs to instantiate
// 生成するアバタープレハブのリスト
public GameObject[] avatarPrefabs;
void Start()
{
// Set the player's nickname (randomized number)
// プレイヤー名を設定(ランダムな番号を付与)
PhotonNetwork.NickName = "Player" + Random.Range(1, 999);
// Connect to the Photon server
// Photonサーバーに接続
PhotonNetwork.ConnectUsingSettings();
}
// Called when connected to the master server
// マスターサーバーに接続されたときに呼ばれる
public override void OnConnectedToMaster()
{
// Join or create a room named "Room"
// "Room" という名前のルームに参加、なければ作成
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions(), TypedLobby.Default);
}
// Called when successfully joined a room
// ルームに参加したときに呼ばれる
public override void OnJoinedRoom()
{
// Choose avatar based on the player's unique ID
// プレイヤーのIDに基づいてアバターを選択
int index = (PhotonNetwork.LocalPlayer.ActorNumber - 1) % avatarPrefabs.Length;
// Randomize the starting position
// 初期位置をランダムに設定
Vector3 pos = new Vector3(Random.Range(-3, 3), 0, Random.Range(-3, 3));
// Instantiate the avatar for this player
// プレイヤー用のアバターを生成
PhotonNetwork.Instantiate(avatarPrefabs[index].name, pos, Quaternion.identity);
}
}
AvatarAnimationSync.cs
歩行状態の同期
using UnityEngine;
using Photon.Pun;
// Synchronizes animation state across the network
// アニメーションの状態をネットワークで同期する
public class AvatarAnimationSync : MonoBehaviourPun, IPunObservable
{
private Animator animator;
private bool isWalking;
void Start()
{
// Get Animator component
// Animatorコンポーネントを取得
animator = GetComponent<Animator>();
}
void Update()
{
// Update walking animation state
// 歩行アニメーションの状態を更新
animator.SetBool("IsWalking", isWalking);
}
public void SetIsWalking(bool walking)
{
// Update walking state locally
// ローカルで歩行状態を更新
isWalking = walking;
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
// Send walking state to other players
// 自分の歩行状態を他のプレイヤーに送信
stream.SendNext(isWalking);
}
else
{
// Receive walking state from other players
// 他のプレイヤーから歩行状態を受信
isWalking = (bool)stream.ReceiveNext();
}
}
}
AvatarNameLabel.cs
頭上にプレイヤー名を表示
using UnityEngine;
using Photon.Pun;
using TMPro;
// Displays the player's name above the avatar
// アバターの頭上にプレイヤー名を表示する
public class AvatarNameLabel : MonoBehaviourPun
{
[SerializeField] private TextMeshPro nameLabel;
void Start()
{
// Set the name to "PlayerX" where X is the player number
// プレイヤー番号を元に "PlayerX" の名前を表示
nameLabel.text = $"Player{photonView.OwnerActorNr}";
}
void LateUpdate()
{
// Make the name label always face the camera
// 名前ラベルが常にカメラ方向を向くように調整
nameLabel.transform.rotation = Camera.main.transform.rotation;
}
}
AvatarMovementController.cs
アバターの移動制御(前回のスクリプトをアップデート)
using UnityEngine;
using Photon.Pun;
// Handles player movement and animation
// プレイヤーの移動とアニメーションの制御を行う
public class AvatarMovementController : MonoBehaviourPun
{
private Animator animator;
private AvatarAnimationSync animationSync;
void Start()
{
// Get Animator component attached to the avatar
// アバターにアタッチされているAnimatorコンポーネントを取得
animator = GetComponent<Animator>();
// Get the animation sync script
// アニメーション同期用スクリプトを取得
animationSync = GetComponent<AvatarAnimationSync>();
}
void Update()
{
// Only the local player can control this avatar
// 自分のアバターのみ操作できるように制限
if (!photonView.IsMine) return;
// Get horizontal (left/right) and vertical (forward) input values
// 水平方向(左右)および垂直方向(前進)の入力を取得
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
// Determine if the avatar is walking
// 移動キーが押されているかどうかで「歩行中」かを判定
bool isWalking = Mathf.Abs(h) > 0.1f || Mathf.Abs(v) > 0.1f;
// Pass the walking state to the Animator
// 歩行状態をAnimatorに伝える
animator.SetBool("IsWalking", isWalking);
// Synchronize walking state over the network
// ネットワーク同期用スクリプトに歩行状態を送信
if (animationSync != null)
animationSync.SetIsWalking(isWalking);
// Rotate the avatar based on left/right input
// 左右キーに応じてアバターを回転
transform.Rotate(0, h * 100f * Time.deltaTime, 0);
// Move the avatar forward if up key is pressed
// 上キーが押されたらアバターを前進させる
if (v > 0) transform.Translate(Vector3.forward * 2f * Time.deltaTime);
}
}
AvatarCameraFollow.cs
自分のアバターにカメラを追従(前回のスクリプトをアップデート)
using UnityEngine;
using Photon.Pun;
// Controls the camera to follow the local player's avatar
// ローカルプレイヤーのアバターにカメラを追従させる
public class AvatarCameraFollow : MonoBehaviourPun
{
void Start()
{
// Only create a camera for the local player's avatar
// 自分のアバターにのみカメラを生成する
if (!photonView.IsMine) return;
// Remove existing AudioListeners to avoid conflicts
// 他のAudioListenerを削除(複数あるとエラーになる)
foreach (var listener in FindObjectsByType<AudioListener>(FindObjectsSortMode.None))
{
Destroy(listener);
}
// Create a new camera object and set it up
// 新しいカメラオブジェクトを生成し設定する
GameObject camObj = new GameObject("PlayerCamera");
camObj.tag = "MainCamera";
camObj.AddComponent<Camera>();
camObj.AddComponent<AudioListener>();
// Attach the camera to the avatar and position it
// カメラをアバターにアタッチし、位置を設定
camObj.transform.SetParent(transform);
camObj.transform.localPosition = new Vector3(0, 2, -3);
camObj.transform.localRotation = Quaternion.Euler(13, 0, 0);
}
}
アタッチ手順
- 2体のアバターを選択 →
Inspector > Add Component
→AvatarMovementController
AvatarCameraFollow
AvatarAnimationSync
AvatarNameLabel
を追加
Hierarchy
> 右クリック >Create Empty
→ GameObjectの名前をMultiplayerManager
に変更MultiplayerManager
にMultiplayerManager.cs
をアタッチInspector
でAvatar Prefabs
の + をクリックし、2体のVRoidアバターを追加



テキスト表示の設定手順
TextMeshProの導入
Hierarchy
のアバターを選択した状態で右クリック
→3D Object > Text-TextMeshPro
- 「Import TMP Essentials」をクリック(自動的にフォント素材がインポートされる)
- 「Import TMP Examples & Extras」もクリック


TextMeshProの配置
- 作成した
Text (TMP)
をアバターの頭上に移動- フォントサイズは
「2」
程度に設定 Alignment
で「Center」と「Middle」を「中央寄せ」
に設定RectTransform > Pos Y
を「1.8」
程度に設定
- フォントサイズは
テキストの位置は、アバターの頭上に!
サイズや位置はアバターによって変えてください!

名前のリンク付け
Text (TMP)
の名前を
,Text (TMP)_A
(任意)に変更 (アバターを判別できる名前に変更)Text (TMP)_B
Hierarchy
から2体のアバターをそれぞれ選択し、Inspector
からAvatarNameLabel
コンポーネントのNameLabel
の項目にText (TMP)_A, B
を選択


Prefab化とメインカメラの消去
Photon対応のPrefab設定
Hierarchy
の2体のアバターを選択Inspector
→Add Component
Photon View
Photon Transform View

Prefabとして保存
Hierarchy
の2体のアバターを選択Assets > Photon > PhotonUnityNetworking > Resources
フォルダにドラッグ&ドロップ「Original Prefab」
をクリックResources
内に2体のアバターが表示されていることを確認


非表示設定
Hierarchy
の2体のアバターを選択Inspector
の上部にあるチェックを外して非表示状態にする

メインカメラの消去
Hierarchy
の 「Main Camera」
を消去する

最終ビルドと確認
ビルド手順
▶️ Play
を押し、エラーがないか確認File > Edit > Project Settings...
をクリックPlayer > Resolution and Presentation > Default Is Native Resolution
からFullScreen Window > Windowed
に変更(Windowのサイズは任意で変更)




4. Build And Run
をクリック
5. test
など適当な名前にし、任意の場所に保存
6. これでWindowが立ち上がったら成功です!



ローカルテスト
Build And Run
後、Unityの画面でも「Play」
を押す- デスクトップでビルドした実行ファイルを2つ起動する

確認ポイント
チェック項目 | 内容 |
---|---|
✅ プレイヤーが2体表示される | エディタとビルドしたゲーム両方に登場 |
✅ 名前が正しく表示されている | Player1 , Player2 のように表示 |
✅ キーボード入力で動く | 各自の画面で別々に動かせる |
✅ カメラは個別追従 | 他のプレイヤーのカメラが干渉しない |
✅ 歩行アニメーションが同期 | 片方が歩けば、相手にも反映される |
全て上手く動作すれば成功です!
まとめ
今回の手順を通じて、以下のことができるようになりました
・Photonを使ったマルチプレイ接続
・アバター生成とカメラ追従
・他プレイヤーとのアニメーション同期
・頭上の名前表示の同期
ぜひ、これをベースにご自身のプロジェクトに活用してください!