質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

同期

複数のディレクトリに存在するファイルを更新した場合に、すべてのファイルにも更新が行われる事、又は、同じ記憶領域に同時にアクセスして内容の整合性が失われてしまう事をを防ぐ制御などを同期と呼びます。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

4485閲覧

UnityのPUN2で描画処理をうまく同期させたい

sh0i

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

同期

複数のディレクトリに存在するファイルを更新した場合に、すべてのファイルにも更新が行われる事、又は、同じ記憶領域に同時にアクセスして内容の整合性が失われてしまう事をを防ぐ制御などを同期と呼びます。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2020/05/29 17:18

前提・実現したいこと

Unityでオンラインマルチプレイ対応のお絵描きアプリ(スマホ版お絵かきの森のようなもの)を作成しています。
PUN2を使って同期処理の実装をしているのですが、描画の部分での同期がうまくいきません。
同期自体はできるのですが、実際に描画した側と、同期をとって描画された側とでかなり描画に差が出てしまいます。
下記の画像のような感じになります。
ちなみに描画自体は、下記のスクリプトをアタッチした「LineRenderer」をコンポーネントとして持つオブジェクト(プレハブ)をPhotonNetwork.Instantiateで生成して行っています。

発生している問題

イメージ説明
Update関数での描画処理に対して、IPunObservable.OnPhotonSerializeViewでのポジションの値の送受信が少ない?為か、
同期した側がカクカクで描画されてしまう。

該当のソースコード

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5using Photon.Pun; 6 7public class Paint : MonoBehaviourPunCallbacks, IPunObservable 8{ 9 LineRenderer paint; //描画担当 10 Vector3 mousePosition; //タッチした座標 11 Vector3 position; //タッチした座標を変換した値 12 int count; //頂点の数 13 bool paintOn; //描画するかどうか 14 15 void Start() 16 { 17 paintOn = true; 18 //paintの初期設定 19 paint = GetComponent<LineRenderer>(); 20 paint.startWidth = 0.1f; 21 paint.endWidth = 0.1f; 22 paint.material.color = Color.black; 23 24 mousePosition = Input.mousePosition; //タッチした場所の座標を取得 25 position = Camera.main.ScreenToWorldPoint(mousePosition); //座標の変換 26 if((2.7f > position.x) && (position.x > -2.7f) && (2.2f > position.y) && (position.y > -3.4f)){ //描画範囲内か確認 27 count += 1; 28 paint.positionCount = count; //頂点の数設定 29 paint.SetPosition(count - 1, new Vector3(position.x, position.y, 1f)); //描画処理 30 } 31 } 32 33 34 void Update() 35 { 36 if(paintOn == true){ 37 if(Input.GetMouseButton(0)){ 38 mousePosition = Input.mousePosition; 39 position = Camera.main.ScreenToWorldPoint(mousePosition); 40 if((2.7f > position.x) && (position.x > -2.7f) && (2.2f > position.y) && (position.y > -3.4f)){ 41 count += 1; 42 paint.positionCount = count; 43 paint.SetPosition(count - 1, new Vector3(position.x, position.y, 1f)); 44 } 45 } 46 if(Input.GetMouseButtonUp(0)){ 47 paintOn = false; //指を離したら、このオブジェクトでの描画を終わる 48 } 49 } 50 } 51 52 void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){ 53 if(stream.IsWriting){ 54 if(count > 0){ 55 stream.SendNext(position.x); 56 stream.SendNext(position.y); 57 } 58 } else { 59 //描画した側の座標を受け取って、他クライアント側でも描画する 60 position.x = (float)stream.ReceiveNext(); 61 position.y = (float)stream.ReceiveNext(); 62 paint = GetComponent<LineRenderer>(); 63 Vector3 pos = new Vector3(position.x, position.y, 1f); 64 count += 1; 65 paint.positionCount = count; 66 paint.SetPosition(count - 1, pos); 67 } 68 } 69}

考えた対策案

①数秒ごとに同期させて、完全なリアルタイムではなく、徐々に絵を他のクライアントに見せる。
②同期できるポジションの値をどうにかして増やす。
③カメラを同期させて、描画している部分を映す。(カメラの同期なんてない?)
などを考えてみましたが、正直①~③のどれもどうやっていいのか分かりません。
①~③のどれかは実現できるのか、他に対策があるのか、そもそも描画処理自体を別の方法で行った方が良いのかなど、
何かアドバイスが欲しいです。よろしくお願いします!

補足情報

PCはWindows10を使用しています。
Unityのバージョンは2019.3.13です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

そのままでは転送量が多すぎて改良が必要ですが、こんな感じでとりあえず描けます。

csharp

1using UnityEngine; 2using Photon.Pun; 3using System.Collections.Generic; 4using System.Linq; 5 6[RequireComponent(typeof(LineRenderer), typeof(PhotonView))] 7public class NetworkLineDrawingController : MonoBehaviour 8{ 9 LineRenderer m_line; 10 PhotonView m_view; 11 /// <summary>描画中フラグ</summary> 12 bool m_isPainting; 13 /// <summary>非オーナー側でのみ利用する。送られてきた Line Renderer の Positions を追加する。</summary> 14 SortedDictionary<int, Vector3> pDict = new SortedDictionary<int, Vector3>(); 15 16 void Start() 17 { 18 Camera.main.orthographic = true; 19 m_line = GetComponent<LineRenderer>(); 20 m_view = GetComponent<PhotonView>(); 21 } 22 23 void Update() 24 { 25 if (PhotonNetwork.IsConnected && !m_view.IsMine) return; // 接続していない時に描画可能にすることで、シーンにただ置くだけで描画動作確認だけはできる 26 27 if (m_isPainting) 28 { 29 if (Input.GetMouseButton(0)) 30 { 31 Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition); 32 pos.z = 0; 33 m_line.positionCount++; 34 m_line.SetPosition(m_line.positionCount - 1, pos); 35 SendLinePosition(m_line.positionCount - 1, pos); 36 } 37 if (Input.GetMouseButtonUp(0)) 38 { 39 m_isPainting = false; 40 m_view.RPC("ResetLinePositions", RpcTarget.Others, null); 41 } 42 } 43 else 44 { 45 if (Input.GetMouseButtonDown(0)) 46 { 47 m_line.positionCount = 0; 48 m_isPainting = true; 49 } 50 } 51 } 52 53 /// <summary> 54 /// Line Renderer の Potision を RPC で送る。オーナーのみが呼ぶことを想定している。 55 /// </summary> 56 /// <param name="index"></param> 57 /// <param name="position"></param> 58 void SendLinePosition(int index, Vector3 position) 59 { 60 object[] p = { (object)index, (object)position }; 61 m_view.RPC("SetLinePosition", RpcTarget.Others, p); 62 } 63 64 /// <summary> 65 /// Line Renderer の Position を受け取る。 66 /// </summary> 67 /// <param name="index"></param> 68 /// <param name="position"></param> 69 [PunRPC] 70 void SetLinePosition(int index, Vector3 position) 71 { 72 // 辞書に追加して index でソートし、position のみを配列に変換して Line Renderer の Positions にセットする。 73 // Position がオーナー側で描画された順番に送られてくるとは限らないためこのような実装にしている。 74 pDict.Add(index, position); 75 Vector3[] positions = pDict.Values.ToArray(); 76 m_line.positionCount = positions.Length; 77 m_line.SetPositions(positions); 78 } 79 80 /// <summary> 81 /// 辞書をクリアする。次に SetLinePosition が呼ばれた時に新しい線を描くために呼ぶ。 82 /// </summary> 83 [PunRPC] 84 void ResetLinePositions() 85 { 86 Debug.Log("Enter ResetLinePositions"); 87 pDict.Clear(); 88 } 89}

実行中の動画
イメージ説明

このコードのままではマウスが止まっている時も毎フレーム Position を送ってしまうので、MonoBehaviour.OnMouseDrag() で Position を追加・送信するなど転送量を減らす工夫が必要だと思います。また、クライアント2つでとりあえずテストしただけで、3人以上とかのいろいろなケースは考慮してません。

投稿2020/07/20 20:23

編集2020/07/20 20:39
bboydaisuke

総合スコア5308

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bboydaisuke

2020/07/20 20:34

observe というのは毎フレーム監視するわけではないし、stream は普通に間が抜けます。 全ての Positions を確実に送るために、Position が増えるたびにその Position を RPC で送るというのが基本的なアイデアです。 途中で順番が狂ったり通信速度が落ちて一時的に乱れたり遅くなったりすることはあるでしょうが、なるべくリアルタイムに描画して、かつ最終的にオーナー側と同じ Line になるであろう実装を試みました。
sh0i

2020/07/23 03:01

bboydaisukeさん、回答ありがとうございます! 一応自己解決できたのですが、回答頂いたような方法とは違ったので勉強になります。今回のような場合だとRPCの使用が基本なんですね。よりベターな方法を見つけれるようにもっと学びを深めたいと思います。回答助かりました。
guest

0

一応自己解決したので、コードを載せておきます。
参考程度に見てください。何かご指摘あればご教授ください。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5using Photon.Pun; 6public class Paint : MonoBehaviourPunCallbacks, IPunObservable 7{ 8 LineRenderer paint; //描画担当 9 Vector3 mousePosition; //タッチした座標 10 Vector3 position; //タッチした座標を変換した値 11 int count; //頂点の数 12 bool paintOn; //描画するかどうか 13 List<Vector3> receivePosition = new List<Vector3>(); //受信側で扱うポジションのリスト 14 int receiveCount; //受信側で扱うポジションのインデックス番号 15 bool receivePaint = false; //受信側で描画するかどうか 16 17 void Start() 18 { 19 paintOn = true; 20 //paintの初期設定 21 paint = GetComponent<LineRenderer>(); 22 paint.startWidth = 0.05f; 23 paint.endWidth = 0.05f; 24 paint.material.color = Color.black; 25 paint.startColor = Color.black; 26 paint.endColor = Color.black; 27 28 if(photonView.IsMine){ 29 mousePosition = Input.mousePosition; //タッチした場所の座標を取得 30 position = Camera.main.ScreenToWorldPoint(mousePosition); //座標の変換 31 if((2.7f > position.x) && (position.x > -2.7f) && (2.2f > position.y) && (position.y > -3.4f)){ //描画範囲内か確認 32 count += 1; 33 paint.positionCount = count; //頂点の数設定 34 paint.SetPosition(count - 1, position); //描画処理 35 receivePosition.Add(position); 36 } 37 } 38 } 39 40 41 void Update() 42 { 43 if(photonView.IsMine){ 44 if(paintOn == true){ 45 if(Input.GetMouseButton(0)){ 46 mousePosition = Input.mousePosition; 47 position = Camera.main.ScreenToWorldPoint(mousePosition); 48 if((2.7f > position.x) && (position.x > -2.7f) && (2.2f > position.y) && (position.y > -3.4f)){ 49 count += 1; 50 paint.positionCount = count; 51 paint.SetPosition(count - 1, new Vector3(position.x, position.y, 1f)); 52 receivePosition.Add(position); 53 } 54 } 55 if(Input.GetMouseButtonUp(0)){ 56 paintOn = false; //指を離したら、このオブジェクトでの描画を終わる 57 } 58 } 59 } 60 } 61 62 void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){ 63 if(stream.IsWriting){ 64 stream.SendNext(count); 65 stream.SendNext(paintOn); 66 //描画したポジションを送信する 67 for(int i = receiveCount; receiveCount < count; receiveCount++){ 68 stream.SendNext(receivePosition[receiveCount]); 69 } 70 } else { 71 //描画した人の座標を受け取って、他クライアント側でも描画する 72 count = (int)stream.ReceiveNext(); 73 paintOn = (bool)stream.ReceiveNext(); 74 if(paintOn == true){ 75 receivePaint = true; 76 } 77 if(receivePaint == true){ 78 paint = GetComponent<LineRenderer>(); 79 for(int i = receiveCount; receiveCount < count; receiveCount++){ 80 position = (Vector3)stream.ReceiveNext(); 81 receivePosition.Add(position); 82 paint.positionCount = receiveCount + 1; 83 paint.SetPosition(receiveCount, new Vector3(receivePosition[receiveCount].x, receivePosition[receiveCount].y, 1f)); 84 } 85 } 86 if(paintOn == false){ 87 receivePaint = false; 88 } 89 } 90 } 91} 92

投稿2020/07/23 03:11

sh0i

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bboydaisuke

2020/07/23 03:23

全ての position を毎回送り直してたりしませんか?もしそうだったら転送データに無駄が多すぎる(転送量が不必要に大きくなりすぎる)と思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問