このチュートリアルでは、ARCore と Nuitrack を使ったマルチプレイヤー ゲームの作成方法を紹介します。AR Football を紹介できて嬉しいです!このゲームには2人以上のプレイヤーが必要です (多いほど良い)。ストライカーであるユーザーが、Android 端末のカメラをテーブルなどのサーフェスに向けると、グリッドが表示され、ARCore オブジェクトである ゴールとゴールキーパーを出現させることができます。ストライカーの目標は、ボールを蹴って、ゴールキーパーとの勝負に勝ってゴールすることです。逆に、ゴールキーパーであるもう一人のプレイヤーは、ボールをキャッチしてゴールを阻止することが目標です。ゴールキーパーは、ゴールを TV 画面またはモニターで見ることができます。複数のストライカーで遊ぶこともできます。Nuitrack はプレイヤーの動きをトラッキングし、同期されたデータがWi-Fi ネットワーク経由で送られます。
完成済みプロジェクトは、 Nuitrack SDK: [Unity 3D] > [NuitrackSDK.unitypackage] > [チュートリアル] > [AR Football]にあります。
このゲームを作成するために必要なものは以下の通りです。
ハードウェア:
ソフトウェア:
- ARCore Unity SDK (このプロジェクト作成には v1.2.1 を使用)
- Nuitrack SDK (このプロジェクト作成には v1.3.3 を使用)
- Unity 2019.2.11f 以上
プロジェクトのセットアップ
- ARCore プロジェクト作成には、ARCore SDK をダウンロードする必要があります。
- 新規 Unity プロジェクトを作成します。
-
[ファイル]メニューの[ビルドの設定]で、Android のプラットフォームを選択します。
-
[プレイヤー設定]で[会社名]と[製品名]のフィールドを入力します。
-
[XR設定]で、[ARCore サポート]を有効にします。
-
[その他の設定 ]で、[Minimum API Level 7.0] (ARCore が必須として指定) を選択し、[Multithreaded Rendering] (ARCore が必須として指定) を無効にし、[Identification]セクションの[パッケージ名]を入力します。
- Arcore SDK と NuitrackSDk.unitypackage を Nuitrack SDK からプロジェクトにインポートします。[Assets] > [Import package] > [Custom Package…]を選択します。
プロジェクトに ARCore を統合
-
このプロジェクトの基となっている HelloAR プロジェクトは、Google による、ARCore を使用した簡単な使用例といえます。新しいシーンを作成し、名前をつけます (例えばStriker)。このプロジェクトに他のオブジェクトが必要なので、[メイン カメラ]と[直接照明]は削除します。HelloAR シーンからすべてのシーンをコピーするために、[Google ARCore] > [Examples] > [HelloAR] > [シーン] > [HelloAR]コマンドを使用します。
- ARCore デバイス: カメラが含まれているオブジェクトです。カメラは、Android 端末の動きに合わせて動きます。 Android 端末のカメラから受け取る画像が、プロジェクトの背景になります。
- キャンバス: 「サーフェスを探しています」のメッセージを表示するために使用されます。
- Example Controller : ゲーミング ARCore サーフェスとのユーザー インタラクションに関与するオブジェクトです。
- Plane Generator: 想像の通り、サーフェスを生成するオブジェクトです。
- Point Cloud: サーフェスを作成するための点群を視覚化する際に使用します。
- 環境オブジェクトを Nuitrack SDK からシーンにドラッグ アンド ドロップします。このオブジェクトはキーパーが守るゴールを象徴するので、非常に重要です。
-
このオブジェクトの動作を定義する C# Script Environment.cs を新規作成します。このスクリプトに含まれるターゲット オブジェクト (ボールのターゲット) へのリンクは、既に、プレハブに関連付けられています。開始時に環境オブジェクトのサイズも設定されます。
using UnityEngine;
using UnityEngine.Networking;
public class Environment :MonoBehaviour {
public Transform aim;
[SerializeField] Vector3 clientSize;
void Start()
{
transform.localScale = clientSize;
}
}
-
ターゲット オブジェクトを aim フィールドにドラッグ アンド ドロップします。環境オブジェクトの[サイズ]フィールドで、(0.1,0.1,0.1) 等、適当なサイズを設定します。
- 便宜上、Example Controller を Football Controller に名前を変更します。HelloARController スクリプトを削除します。その代りに、独自のスクリプト FootballARController.cs を作成しますが、[要素の追加]>[C#スクリプト]>[FootballARController]を使用します。このスクリプトでは、AR とユーザーのインタラクションについて説明します。
- GoogleARCore とUnityEngine.Networking 名前空間をスクリプトに追加します。
-
必要なフィールドをいくつか追加します。
public Camera mainCamera;
GameObject environment;
public GameObject searchingForPlaneUI;
private const float modelRotation = 180.0f;
private List<DetectedPlane> allPlanes = new List<DetectedPlane>();
[SerializeField] Transform aRCoreDevice;
-
Update で検出したサーフェスを取得します。サーフェスの表示/非表示を行う変数を作成します。1つ以上のサーフェスのトラッキングを行っている場合、「サーフェスを探しています」のメッセージは表示されません。
public void Update()
{
Session.GetTrackables<DetectedPlane>(allPlanes);
bool showSearchingUI = true;
for (int i = 0; i < allPlanes.Count; i++)
{
if (allPlanes[i].TrackingState == TrackingState.Tracking)
{
showSearchingUI = false;
break;
}
}
searchingForPlaneUI.SetActive(showSearchingUI);
}
-
Update で、ストライカーの方のユーザーが画面をタッチしたかどうか確認します。
Touch touch;
if (Input.touchCount < 1 || (touch = Input.GetTouch(0)).phase != TouchPhase.Began)
{
return;
}
-
ユーザーが画面をタッチした後に、Update で、レイに関する情報を保存する変数を作成します。レイの原点となった地点の座標を保存します。レイとの衝突するサーフェスを特定するためのフィルターを追加します。
TrackableHit hit;
TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon |
TrackableHitFlags.FeaturePointWithSurfaceNormal;
-
Update で、レイキャストを処理します。サーフェスのトラッキングとレイが正しい方向から投じられるかどうかを確認します (ゴールキーパーの後ろからボールを蹴ることはルール違反になるため)。その後、ボールの投入を処理します。ゴールは、ボールの軌道上に配置されるべきです。そうでない場合は、ゴールとゴールキーパー (Environment) の配置を修正します。このプロジェクトでは2つのレイを使用します。1つは ARCore レイで、サーフェスを検出します。もう1つは Unity レイで、指定した Unity オブジェクト、つまりゴールを検出します。
environment = FindObjectOfType<Environment>();
if (Frame.Raycast(touch.position.x, touch.position.y, raycastFilter, out hit))
{
if ((hit.Trackable is DetectedPlane) &&
Vector3.Dot(mainCamera.transform.position - hit.Pose.position,
hit.Pose.rotation * Vector3.up) < 0)
{
Debug.Log("Hit at back of the current DetectedPlane");
}
else
{
if (KickBall() == false && environment)
{
environment.transform.position = hit.Pose.position;
environment.transform.rotation = hit.Pose.rotation;
environment.transform.Rotate(0, modelRotation, 0, Space.Self);
}
}
}
else
{
KickBall();
}
-
ボールを蹴った直線上にゴールがあるかを確認し、ある場合は、ボールを蹴ります。ユーザーがタッチによって指定した場所に向かってレイを投じます。ボールが何かに触れる場合はターゲット ポイントを置きます。スクリプトで、カメラを Environment の子要素とすることで、Environment との関連におけるカメラの詳細な位置 (座標) を探すことができます。ボールがどこから蹴られるか、その地点を探し出すのに、これらすべてを行う必要があります。シーンにボールを作成し、ボールの位置、回転、子オブジェクト (Environment) を設定します。カメラの設定と同じに設定します。ターゲット ポイントを設定します。カメラを元の位置、ARCore 端末の上に戻します。return メソッドは、ボールが何かに当たる (つまりゴールする) 場合にのみ、true が戻ってきて、それ以外は、false となります。
bool KickBall()
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hitRay;
if (Physics.Raycast(ray, out hitRay, 100) && environment)
{
environment.aim.position = hitRay.point;
mainCamera.transform.parent = envirnoment.transform;
mainCamera.transform.parent = aRCoreDevice.transform;
return true;
}
return false;
}
-
Unity で、FootballController のフィールドを、以下の画像のように設定します。
-
Android 端末を PC に接続し、プロジェクトを実行します。ユーザーが、Android 端末のカメラをテーブルなどのサーフェスに向けると、グリッドが表示されます。ユーザーがグリッドを触ると、ARCore ゴールとゴールキーパーが出現します。デフォルトで、1つのゴールとゴールキーパーが表示されます。これは、プロジェクトの開始後に自動的に作成されます。
- 注意
- Unity エディターから利用可能ですので、ARCore のいくつかの機能を試すためにプロジェクトを組み立てないでください。Android 端末を USB 経由で接続して、プロジェクトを実行するだけです。この機能が不要な場合は、簡単に無効にできます。[編集] > [プロジェクト設定] > [ArCore]を選択し、[インスタント プレビュー有効] のチェックボックスをオフにします。
マルチプレイヤーの作成
複数のプレイヤー (1人のゴールキーパーと複数のストライカー) がゲームに参加するには、サーバーとクライアントがネットワークでつながる必要があります。ゴールキーパーがサーバーとなり、それにストライカーがクライアントとして接続することになります。すべてのプレイヤーが同じ Wi-Fi ネットワーク上にいるべきです。サーバーに接続するには、クライアントが[接続]ボタンを押すだけです。
- Network Manager.cs という、ネットワーク用の標準的な Unity スクリプトを使用します。ストライカー シーンに新しいオブジェクトを追加します。そのためには、[空のオブジェクト] > [ネットワーク マネージャー]を選択し、[ネットワーク マネージャー (スクリプト)]コンポーネントを追加します。
- 環境プレハブを保存後に、シーンから削除します。
-
保存した環境プレハブを[ネットワーク マネージャー]に追加することで、このオブジェクトが生成されるべきであることをシステムに知らせることができます(Network Manager (Script) → Registered Spawnable Prefabs)。
-
Network Discovery スクリプトは、 ローカル ネットワークのサーバー検索を定義します。これも、標準の Unity スクリプトです。このスクリプトを変更することにより、ユーザーにとってよりシンプルなゲームを作成します。デフォルトでは、ユーザーが、検出されたサーバーの中からサーバーを選択する必要があります。そこでよりユーザーに使いやすいよう、サーバーへの接続を自動にしました。新しいスクリプト、QuickConnectNetworkDiscovery.cs を作成します。QuickConnectNetworkDiscovery クラスは、標準の NetworkDiscovery クラスから継承されているべきです。
using UnityEngine.Networking;
public class QuickConnectNetworkDiscovery :NetworkDiscovery {
public override void OnReceivedBroadcast(string fromAddress, string data)
{
base.OnReceivedBroadcast(fromAddress, data);
if(NetworkManager.singleton.IsClientConnected())
return;
NetworkManager.singleton.networkAddress = fromAddress;
NetworkManager.singleton.StartClient();
}
}
- [ネットワーク マネージャー]に、QuickConnectNetworkDiscovery スクリプトをドラッグ アンド ドロップします。
-
Unity で、[ネットワーク マネージャー]を探し、[QuickConnectNetworkDiscovery]を選択し、[ネットワーク マネージャーを使用]チェックボックスをオンにし、[GUIを表示]のチェックボックスをオフにしてデバッグ メニューを非表示にします。
- 新しいスクリプト、NetworkController.cs を作成します。このスクリプトでは、クライアントとサーバーを作成し、クライアント接続時のサーバーアクションを説明します。
- 名前空間、UnityEngine.UI と UnityEngine.Networking を追加します。
-
得点、クライアント/サーバー、テキストボックスのフィールドを追加します。スコア フィールドを隠すことにより、Unity エディターで得点を設定するのを防ぎます。
[HideInInspector]public int score;
public bool isClient;
[Header("Server")]
[SerializeField] Text scoreText;
[SerializeField] Text connectionsText;
[SerializeField] GameObject environmentPrefab;
[Header("Client")]
[SerializeField] Text connectText;
- [ゴールキーパー]シーンが実行されると、サーバーが起動します。
-
StartClient メソッドで、クライアントの初期化が行われ、起動します。同様に、StartServer メソッドでサーバーとホストも初期化され、起動します。サーバーであるゴールキーパーには得点が表示され、クライアントであるストライカーには接続されているプレイヤー数が表示されます。StartClient メソッドは、[接続]ボタンと強く結びついています。
private void Start()
{
if (isClient == false)
{
StartServer();
}
}
public void StartClient()
{
FindObjectOfType<NetworkDiscovery>().Initialize();
FindObjectOfType<NetworkDiscovery>().StartAsClient();
}
void StartServer()
{
FindObjectOfType<NetworkDiscovery>().Initialize();
FindObjectOfType<NetworkDiscovery>().StartAsServer();
NetworkManager.singleton.StartHost();
GameObject environment = (GameObject)Instantiate(environmentPrefab);
NetworkServer.Spawn(environment);
}
- 注意
- Unity でのクライアントとサーバーについての詳細は、こちらをご覧ください。
-
Update で、得点と接続されているプレイヤー数のテキストを更新します。さらに、接続の状態 (接続/未接続) を表示するボタンを追加します。
private void Update()
{
if (isClient == false)
{
scoreText.text = “Scores:” + score.ToString();
connectionsText.text = "connected:" + NetworkManager.singleton.numPlayers;
}
else
{
if(NetworkManager.singleton.IsClientConnected())
connectText.text = "Connected";
else
connectText.text = "Connect";
}
}
-
ネットワーク マネージャーに、NetworkController.cs スクリプトをドラッグ アンド ドロップします。[Is Client]をクリックすると、スクリプトがクライアントの動作を説明します。
- [接続]ボタンを表示するキャンバスを作成しますが、[作成] > [UI] > [キャンバス]コマンドを使用します。キャンバスにボタンを作成するには、[UI] > [ボタン] ([接続]ボタン) を使用します。
-
ボタンを選択し、シーンからオブジェクトを追加します。[ボタン] > [OnClick + NetworkManager] > [Function] > [NetworkController] > [StartClient()]を選択すると、ボタンをクリックした時に、StartClient メソッドが呼び出されます。
-
テキストを、[ボタン]から[ネットワーク コントローラー]の[Connect Text]フィールドにドラッグ アンド ドロップします。
- 新しいシーンを作成し、名前をつけます (例えば GoalKeeper)。
-
カメラの位置を (0, 1, -5) に設定するなら、ゴールとゴールキーパーが正しく表示されます。
-
NuitrackSDK.unitypackage から NuitrackScripts プレハブをシーンに追加します。スケルトン トラッキングのために、[スケルトンモジュールオン]のチェックボックスをオンにします。
- このシーンにキャンバスと2つのテキスト フィールドを作成します。2つのテキスト フィールドとは、ScoreText と ConnectedText で、得点 (スコア) と接続状況のテキストを表示するためのフィールドです。テキスト フィールドをキャンバス上に自由に配置します。
ボールの作成
- ストライカーのシーンにボールを作成します。ボールに関して 2つのオブジェクトを作成します。1つ目の Ball は常に一定の場所にとどまり、Environment の子要素となり、もう一つの Ball Model はユーザーがボールを蹴ると動き、Ball の子要素となります。Ball Model オブジェクトのみが同期されます。Empty > Ball を作成し、標準スクリプト Network Transform を追加することで、ネットワーク上の GameObjects の動きや回転を同期します。
-
Network Transform の Network Send Rate を 0 (親オブジェクトを同期しないため) に設定します。その他の設定はそのままにします。
-
Ball にもう一つの要素、Network Transform Child を追加します。この要素の Network Send Rate を 20 (20 packages / 1秒を意味し、ボールの動きが滑らかになります) に設定します。
-
Ball に子 sphere を作成します。Create 3D > Object > Sphere で作成し、例えば、Ball Model と名前を付けます。Ball Model のセットアップを行います。Scale (0.3, 0.3, 0.3)、Position (0, 0, 0)、Rotation (0, 0, 0) に設定します。
-
RigidBody コンポーネントを追加し、[重力を利用]のチェックボックスをオフにします。
-
Ball オブジェクトについて、Network Transform Child > Target > Ball Model を選択します。これにより、子オブジェクトの位置が、サーバーとクライアント間で同期されます。
-
ボールの動作を定義するスクリプト BallController.cs を作成します。ボールの最初の位置を指定する startPosition フィ―ルドと、クライアントとサーバーを差別化 (詳細な座標や位置を設定) する networkController を追加します。
[SerializeField]
GameObject ball;
Vector3 startPosition;
Vector3 endPosition;
float ballSpeed = 3;
Rigidbody rb;
bool inGame = true;
NetworkController networkController;
void Start()
{
rb = GetComponentInChildren<Rigidbody>();
Destroy(gameObject, 7.0f);
transform.parent = FindObjectOfType<Environment>().transform;
transform.localScale = Vector3.one;
transform.localPosition = Vector3.zero;
transform.localEulerAngles = Vector3.zero;
ball.transform.localPosition = startPosition;
networkController = FindObjectOfType<NetworkController>();
}
-
Update で、プレイ中のボールの動きを定義します。ゲーム中のボールは、特定のポイントに移動します。スクリプトと Unity 物理法則に基づくボールの動きが、サーバーによって処理されます。クライアントには、ボールの位置だけが戻されます。
void Update () {
if (inGame && networkController.isClient == false)
{
ball.transform.localPosition = Vector3.MoveTowards(ball.transform.localPosition, endPosition, ballSpeed * Time.deltaTime);
ball.transform.Rotate(Vector3.one * ballSpeed);
}
}
- 注意
- Unity でのVector3.MoveTowards についての詳細は、こちらをご覧ください。
-
Setup メソッドで、ボールの起点と終点を指定します。
public void Setup(Vector3 startPos, Vector3 endPos)
{
endPosition = endPos;
startPosition = startPos;
}
-
ボールが他のオブジェクトに触れると、物理法則が有効になります。ボールが手に触れると、1点が追加されます。
public void OnCollide(Collision collision)
{
if (inGame && networkController.isClient == false)
{
Debug.Log("Ball collide");
if (collision.transform.tag == "Hand")
FindObjectOfType<NetworkController>().score++;
rb.useGravity = true;
inGame = false;
}
}
-
Ball の子要素である Ball Model オブジェクトには、親要素と違ってコライダーが含まれています。新しいスクリプト CollideChecker.cs を作成し、他のオブジェクトと衝突した場合のボールの動作を説明します。
using UnityEngine;
public class CollideChecker :MonoBehaviour
{
private void OnCollisionEnter(Collision collision)
{
GetComponentInParent<BallController>().OnCollide(collision);
}
}
- スクリプトを[Ball Model]にドラッグ アンド ドロップします。
-
BallController.cs スクリプトを Ball にドラッグ アンド ドロップし、Ball Model を Ball フィールドに置きます。
- Ball をプレハブとして保存後に、シーンから削除します。その後、Ball が自動でサーバー上とクライアント上に生成されるよう定義します。そのための設定方法は、Network Manager - Network Manager (Script) > Spawn Info > Registered Spawnable Prefabs > Ball - Ball です。
ストライカーの作成
- Unity で、重要なもう一人のプレイヤーといえるストライカーを作成するには、Empty Object > Player を使用します。
-
ストライカーのアクションを定義する PlayerController.cs を新規作成します。Networking 名前空間をスクリプトに追加します。NetworkBehaviour からクラスを継承し、サーバーからメッセージの送受信ができるようにします。ballPrefab のためのフィールドを作成します。新しいメソッド Kick を作成し、ボールの起点と終点を設定します。このメソッドは、サーバーで呼び出されます。サーバーで呼び出すには、[Command] 属性と Cmd メソッド プレフィックスを追加します。
using UnityEngine;
using UnityEngine.Networking;
public class PlayerController :NetworkBehaviour {
[SerializeField] GameObject ballPrefab;
[Command]
void CmdKick(Vector3 startPos, Vector3 endPos)
{
GameObject ball = (GameObject)Instantiate(ballPrefab);
ball.GetComponent<BallController>().Setup(startPos, endPos);
NetworkServer.Spawn(ball);
}
public void Kick(Vector3 startPos, Vector3 endPos)
{
CmdKick(startPos, endPos);
}
}
- PlayerController.cs を Player Controller オブジェクトにドラッグ アンド ドロップし、Ball を Ball Prefab フィールドにドラッグ アンド ドロップします。Player オブジェクトをプレハブとして保存後に、シーンから削除します。
-
ボールを蹴った時、サーバーにメッセージを送信するを追加します。
bool KickBall()
{
...
if (Physics.Raycast(ray, out hitRay, 100) && environment)
{
...
FindObjectOfType<PlayerController>().Kick(mainCamera.transform.localPosition, environment.aim.transform.localPosition);
...
return true;
}
}
-
NetworkManager > Network Manager (Script) > Spawn Info を選択し、Player を Player Prefab にドラッグ アンド ドロップします (Player プレハブには、Network Identity コンポーネントが必要です)。
- NetworkManager オブジェクトを GoalKeeper シーンにコピーします。
-
GoalKeeper シーンについて、Network Manager の AutoCreatePlayer のチェックボックスをオフにすることで、このシーンに新たなプレイヤーが作成されないようにします (ゴールキーパーは一人で十分なので)。
-
サーバーの Is Client のチェックボックスをオフにします。
-
Network Manager で Server を選択し、得点と接続状態のテキストのフィールド、ScoreText - ScoreText (Text) と Connection Text - Connection Text (Text) を設定します。
-
Environment プレハブを Environment Prefab フィールドにおきます。
-
サーバーの Environment の大きさを維持するには、Environment.cs スクリプトの Start method に以下のコードを追加します。
void Start()
{
if(FindObjectOfType<NetworkIdentity>().isServer == false)
transform.localScale = clientSize;
}
-
Build Settings の GoalKeeper は Android 端末では必要ないので、チェックボックスをオフにします。
-
Player Settings の XR Settings にある ARCore は、TVico では必要ないので、チェックボックスをオフにします。
-
TVico と同様、Android バージョン を選択します。Other Settings > Android (TVico でのバージョンは 5.1.1) を使って設定します。
- USB 経由で TVico と PC をつなぎ、Build and Run をクリックするか、デスクトップに互換性のあるセンサーを接続します。
- プロジェクトの組み立ては2段階に分けられます。
- まず、Android 端末で、ARCore を使って Striker シーンを作成します。
- その後、ARCore を使っていない GoalKeeper シーンを TVico または、センサー付きの PC で組み立てます。
プロジェクト完成まであとわずかです。ストライカーとなるユーザーが、ゴールとゴールキーパーをグリッドに配置し、ボールを蹴ります。そしてゴールキーパーとなるユーザーが TV 画面 / デスクトップ上で、ボールをキャッチしようとします。
しかし、この段階では、ストライカーからは、ゴールキーパーのアバターが動いていないのに、ボールをキャッチしているようです。この問題を取り除くには、サーバーとクライアントを同期する必要があります。
サーバーとクライアントを同期
-
更にいくつかのコードを書き込むことで、アバターの動きを同期させることにします。新しいスクリプトを作成し、ここでは AvatarSync.cs と名前を付けます。
- 注意
- 別の方法として、スクリプトを書き込まずにサーバーとクライアントを同期できます。Network Transform Child の11コンポーネント (10本の骨とアバター) を使用するという方法があります。Ball の同期はこの方法で行いました。
-
Networking 名前空間を追加し、AvatarSync クラスをNetworkBehaviour から継承します。同期を行う骨とアバターの変形を含む配列を追加します。骨の回転やアバターの位置が変わると、サーバーからメッセージが送信されます。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class AvatarSync :NetworkBehaviour
{
[SerializeField] Transform[] syncBones;
[SerializeField] Transform avatar;
}
[ClientRpc]
public void RpcOnBonesTransformUpdate(BonesInfoMessage boneMsg)
{
for (int i = 0; i < boneMsg.bonesRot.Length; i++)
{
syncBones[i].localRotation = boneMsg.bonesRot[i];
}
avatar.localPosition = boneMsg.avatarPos;
}
public class BonesInfoMessage :MessageBase
{
public Quaternion[] bonesRot;
public Vector3 avatarPos;
}
-
サーバーかどうかを FixedUpdate メソッドで確認します。
private void FixedUpdate()
{
if (isServer)
{
BoneUpdate(syncBones);
}
}
-
骨やアバターに関する情報を更新し、メッセージを送信します。
public void BoneUpdate(Transform[] bones)
{
List<Quaternion> rotations = new List<Quaternion>();
for (int i = 0; i < bones.Length; i++)
rotations.Add(bones[i].localRotation);
BonesInfoMessage msg = new BonesInfoMessage
{
bonesRot = rotations.ToArray(),
avatarPos = avatar.position,
};
RpcOnBonesTransformUpdate(msg);
}
- スクリプトを[環境]プレハブにドラッグ アンド ドロップします。
-
RiggedAvatar オブジェクトを[アバター]フィールドに配置します。Sync Bones 配列に骨を入力していきます。骨は Rigged Avatar 配列の Rigged Avatar オブジェクトにあります。Rigged Model > Model Joints で、合計10本の骨を選択する必要があります。
- プロジェクトを実行します。これで、すべての準備ができました。サーバーとクライアントが同期され、アバターも動いていますので、みんなでゲームを楽しめます!