Unity Photon1でチャットウィンドウとプレイヤー頭上UIをシーン遷移後も保持したい
解決したい事
UnityでPhoton1を使っています。
文字チャット機能を持ったゲームを作っています。
文字チャットはチャットウィンドウとプレイヤーアバターの頭上にと2つ同時に表示される仕組みです。
大まかな仕様は下図の様になっております。
ですが、シーンAからシーンBに遷移後に下記の状態になってしまいます。
・シーンBへの遷移後→チャットウィンドウそのものが表示されなくなってしまう。
・シーンAへ再度遷移後→チャットウィンドウは表示されるが、頭上チャットが表示されなくなってしまう。
シーン遷移をしても各チャットが機能するにはどうすれば良いかご教授頂ければ。。。
該当するソースコード
GameManager
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using Random = UnityEngine.Random;
public class GameManagerScript : Photon.PunBehaviour
{
//誰かがログインする度に生成するプレイヤーPrefab
//public GameObject playerPrefab;
public string playerName;
void Start()
{
PhotonNetwork.ConnectUsingSettings("0.1");
if (!PhotonNetwork.connected) //Phootnに接続されていなければ
{
SceneManager.LoadScene("Launcher"); //ログイン画面に戻る
return;
}
playerName = "";
int _ranNum = Random.Range(1, 3);
playerName = "Player" + _ranNum;
var position = new Vector3(0, 5, 0);
/*GameObject avatar = PhotonNetwork.Instantiate(playerName, position, Quaternion.identity, 0); */
if (playerName == null)
{
Debug.Log(" NoPlayerName");
}
else
{
if (PlayerManager.LocalPlayerInstance == null)
{
GameObject avatar = PhotonNetwork.Instantiate(playerName, position, Quaternion.identity, 0);
}
/* GameObject obj = (GameObject)Resources.Load("ChatManager");
Instantiate(obj, new Vector3(0, 0, 0), Quaternion.identity);*/
//Photonに接続していれば自プレイヤーを生成
//GameObject Player = PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f, 0f, 0f), Quaternion.identity, 0);
}
// Update is called once per frame
void Update()
{
}
}
}
InRoomChat
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(PhotonView))]
public class InRoomChat : Photon.MonoBehaviour
{
#region 変数宣言
//範囲チャット実装のためのオブジェクト、変数定義
GameObject[] players; //全てのプレイヤーキャラ取得用
GameObject sender; //送信キャラ取得用
GameObject myPlayer; //自分のキャラ取得用
PlayerManager myPM; //自分のキャラのPlayerManager取得用
GUIStyle ChatStyle = new GUIStyle(); //範囲チャットStyle
GUIStyleState ChatStyleState = new GUIStyleState();
GUIStyle AllChatStyle = new GUIStyle(); //全体チャットStyle
GUIStyleState AllChatStyleState = new GUIStyleState();
public Rect GuiRect = new Rect(0, 0, 300, 200); //チャットUIの大きさ設定用
public bool IsVisible = true; //チャットUI表示非表示フラグ
public bool AlignBottom = true;
public List<string> messages = new List<string>(); //チャットログ格納用List
public List<bool> chatKind = new List<bool>(); //チャットログの種類格納用(範囲チャor全チャ)
public string inputLine = "";//入力文章格納用String
private Vector2 scrollPos = Vector2.zero; //スクロールバー位置
#endregion
#region Start関数 Updata関数
public void Start()
{
//myPlayerオブジェクト取得(範囲チャット発言時にpositionとmyPM使う)
GetmyPlayer();
//範囲チャットの場合は白文字にし、文字がUIからあふれた場合は折り返す設定
ChatStyleState.textColor = Color.white;
ChatStyle.normal = ChatStyleState;
ChatStyle.wordWrap = true;
//全体チャットの場合は赤文字にし、文字がUIからあふれた場合は折り返す設定
AllChatStyleState.textColor = Color.red;
AllChatStyle.normal = AllChatStyleState;
AllChatStyle.wordWrap = true;
}
public void Update()
{
//ChatUIの位置を調整
this.GuiRect.y = Screen.height /3;
//ChatUIの大きさ調整
GuiRect.width = Screen.width / 4;
GuiRect.height = Screen.height / 3;
}
#endregion
#region OnGUI関数
public void OnGUI()
{
if (!this.IsVisible || !PhotonNetwork.inRoom) //表示フラグがOFFまたはphotonにつながっていないとき
{
//UI非表示
return;
}
//ChatUIの作成開始
//チャットUI生成 Begin&EndAreaでチャットUIの位置と大きさを設定
GUILayout.Window(0, GuiRect, ChatUIWindow, ""); //チャットUIウインドウを作成
//Enterを押すと
if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
{
//チャット入力待ち状態にする
GUI.FocusControl("ChatInput");
}
}
#endregion
#region チャットUI生成
void ChatUIWindow(int windowID)
{
//FocusがチャットUIに乗ってるときにEnterを押すとチャット発言が実行される
if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
{
if (!string.IsNullOrEmpty(this.inputLine)) //チャット入力欄がNullやEmptyでない場合
{
//範囲チャット送信関数実行
SendChat(false);
return;
}
}
//垂直のコントロールグループ開始
GUILayout.BeginVertical();
//スクロールビュー開始位置
scrollPos = GUILayout.BeginScrollView(scrollPos);
//チャットログ表示用フレキシブルスペース生成
GUILayout.FlexibleSpace();
//フレキシブルスペースにチャットログを表示
for (int i = 0; i <= messages.Count - 1; i++)
{
if (chatKind[i] != true) //範囲チャットであれば
{
GUILayout.Label(messages[i], ChatStyle);
}
else //全チャットであれば
{
GUILayout.Label(messages[i], AllChatStyle);
}
}
//スクロールビュー終了
GUILayout.EndScrollView();
//水平のコントロールグループ開始
GUILayout.BeginHorizontal();
//入力テキストフィールド生成、Focusが乗った状態をChatInputと命名
GUI.SetNextControlName("ChatInput");
inputLine = GUILayout.TextField(inputLine, 200);
//「Send」ボタンを生成かつ押したときには範囲チャット送信
if (GUILayout.Button("Send", GUILayout.ExpandWidth(false)))
{
//範囲チャット送信関数実行
SendChat(false);
}
//Allボタンを生成かつ押したときには全体チャット送信
if (GUILayout.Button("All", GUILayout.ExpandWidth(false)))
{
//全体チャット送信関数実行
SendChat(true);
}
//水平のコントロールグループ終了
GUILayout.EndHorizontal();
//垂直のコントロールグループ終了
GUILayout.EndVertical();
}
#endregion
#region GetmyPlayer 自キャラのオブジェクトをmyPlayerに登録
public void GetmyPlayer()
{
//自キャラのID取得
int myPlayerID = PhotonNetwork.player.ID;
//全てのプレイヤーオブジェクトを取得
players = GameObject.FindGameObjectsWithTag("Player");
//全てのプレイヤーオブジェクトから自キャラをIDで検索し、取り出す
foreach (GameObject player in players)
{
int playerLoopId = player.GetComponent<PhotonView>().owner.ID;
if (playerLoopId == myPlayerID)
{
//自プレイヤーオブジェクトを取得
myPlayer = player;
//自キャラの頭上にチャット表示するためにPlayerManager取得
myPM = myPlayer.GetComponent<PlayerManager>();
}
}
return;
}
#endregion
#region チャット送信関数
void SendChat(bool isAll)
{
//chatRPC
this.photonView.RPC("Chat", PhotonTargets.All, myPlayer.transform.position, this.inputLine, isAll);
//頭上にチャット表示するためにstring送信
myPM.setChat(this.inputLine);
//送信後、入力欄を空にし、スクロール最下位置に移動
this.inputLine = "";
scrollPos.y = Mathf.Infinity;
}
#endregion
#region ChatRPC RPC呼出側:送信者 RPC受信側:受信者
[PunRPC]
public void Chat(Vector3 senderposition, string newLine, bool isAll, PhotonMessageInfo mi)
{
if (messages.Count >= 100) //チャットログが多くなって来たらログを削除してから受信
{
messages.Clear(); //全てのチャットログを削除
chatKind.Clear(); //全てのチャットの種類情報削除
}
if (!isAll) //範囲チャとして受信
{
//myPlayerとsenderの距離から受信するか判断
if (Vector3.Distance(myPlayer.transform.position, senderposition) < 10)
{
//chat受信
ReceiveChat(newLine, isAll, mi);
}
}
else if (isAll) //全チャとして受信
{
//chat受信
ReceiveChat(newLine, isAll, mi);
}
//受信したときはスクロール最下位置
scrollPos.y = Mathf.Infinity;
}
#endregion
#region チャット受信関数
void ReceiveChat(string _newLine, bool isAll, PhotonMessageInfo _mi)
{
//送信者の名前用変数
string senderName = "anonymous";
if (_mi.sender != null)
{
//送信者の名前があれば
if (!string.IsNullOrEmpty(_mi.sender.NickName))
{
senderName = _mi.sender.NickName;
}
else
{
senderName = "player " + _mi.sender.ID;
}
}
//受信したチャットをログに追加
this.messages.Add(senderName + ": " + _newLine);
this.chatKind.Add(isAll);
return;
}
#endregion
public void AddLine(string newLine)
{
this.messages.Add(newLine);
}
}
PlayerManager
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System;
public class PlayerManager : Photon.PunBehaviour, IPunObservable
{
//頭上のUIのPrefab
public GameObject PlayerUiPrefab;
//現在のHP
public int HP = 100;
//Localのプレイヤーを設定
public static GameObject LocalPlayerInstance;
//チャット同期用変数
public string ChatText = "";
private bool isRunning;
Coroutine ChatCoroutine;
//頭上UIオブジェクト
GameObject _uiGo;
#region プレイヤー初期設定
void Awake()
{
if (photonView.isMine)
{
PlayerManager.LocalPlayerInstance = this.gameObject;
}
}
#endregion
#region 頭上UIの生成
void Start()
{
if (PlayerUiPrefab != null)
{
//Playerの頭上UIの生成とPlayerUIScriptでのSetTarget関数呼出
_uiGo = Instantiate(PlayerUiPrefab) as GameObject;
_uiGo.SendMessage("SetTarget", this, SendMessageOptions.RequireReceiver);
}
else
{
Debug.LogWarning("<Color=Red><a>Missing</a></Color> PlayerUiPrefab reference on player Prefab.", this);
}
}
#endregion
void Update()
{
if (!photonView.isMine) //このオブジェクトがLocalでなければ実行しない
{
return;
}
//LocalVariablesを参照し、現在のHPを更新
HP = LocalVariables.currentHP;
}
#region 頭上Chatの表示
public void setChat(string inputLine)
{
//コルーチンが動作中であれば
if (isRunning)
{
StopCoroutine(ChatCoroutine); //コルーチンを停止
ChatCoroutine = null; //削除
isRunning = false;
}
//頭上チャット用Stringに入力文字列を格納
ChatText = inputLine;
//新しいコルーチンを生成
ChatCoroutine = StartCoroutine(_ChatText(6f));
}
//頭上チャット表示用コルーチン
IEnumerator _ChatText(float pausetime)
{
//コルーチン動作フラグON
isRunning = true;
//pausetimeの間、頭上チャットを表示し続ける(今は6秒に設定)
yield return new WaitForSeconds(pausetime);
//頭上チャットを非表示にする
ChatText = "";
//コルーチン動作フラグOFF
isRunning = false;
}
#endregion
#region OnPhotonSerializeView同期
//プレイヤーのHP,チャットを同期
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.isWriting)
{
stream.SendNext(this.HP);
stream.SendNext(this.ChatText);
}
else
{
this.HP = (int)stream.ReceiveNext();
this.ChatText = (string)stream.ReceiveNext();
}
}
#endregion
}
CharacterControlScript
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class CharacterControlScript : Photon.PunBehaviour
{
public static GameObject LocalPlayerInstance;
GameObject avatar;
//オンライン化に必要なコンポーネントを設定
public PhotonView myPV;
public PhotonTransformView myPTV;
private Camera mainCam;
//移動処理に必要なコンポーネントを設定
public Animator animator; //モーションをコントロールするためAnimatorを取得
public CharacterController controller; //キャラクター移動を管理するためCharacterControllerを取得
//移動速度等のパラメータ用変数(inspectorビューで設定)
public float speed; //キャラクターの移動速度
public float jumpSpeed; //キャラクターのジャンプ力
public float rotateSpeed; //キャラクターの方向転換速度
public float gravity; //キャラにかかる重力の大きさ
Vector3 targetDirection; //移動する方向のベクトル
Vector3 moveDirection = Vector3.zero;
// Start関数は変数を初期化するための関数
private void Awake()
{
if( photonView.isMine)
{
CharacterControlScript.LocalPlayerInstance = this.gameObject;
}
DontDestroyOnLoad(this.gameObject);
}
void Start()
{
GetCamera();
/*
if (myPV.isMine) //自キャラであれば実行
{
//MainCameraのtargetにこのゲームオブジェクトを設定
mainCam = Camera.main;
mainCam.GetComponent<CameraScript>().target = this.gameObject.transform;
} */
// DontDestroyOnLoad(gameObject );
}
// Update関数は1フレームに1回実行される
void Update()
{
if (!myPV.isMine)
{
return;
}
moveControl(); //移動用関数
RotationControl(); //旋回用関数
//最終的な移動処理
//(これが無いとCharacterControllerに情報が送られないため、動けない)
controller.Move(moveDirection * Time.deltaTime);
//スムーズな同期のためにPhotonTransformViewに速度値を渡す
Vector3 velocity = controller.velocity;
myPTV.SetSynchronizedValues(velocity, 0);
}
void moveControl()
{
//★進行方向計算
//キーボード入力を取得
float v = Input.GetAxisRaw("Vertical"); //InputManagerの↑↓の入力
float h = Input.GetAxisRaw("Horizontal"); //InputManagerの←→の入力
//カメラの正面方向ベクトルからY成分を除き、正規化してキャラが走る方向を取得
Vector3 forward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
Vector3 right = Camera.main.transform.right; //カメラの右方向を取得
//カメラの方向を考慮したキャラの進行方向を計算
targetDirection = h * right + v * forward;
//★地上にいる場合の処理
if (controller.isGrounded)
{
//移動のベクトルを計算
moveDirection = targetDirection * speed;
//Jumpボタンでジャンプ処理
if (Input.GetButton("Jump"))
{
moveDirection.y = jumpSpeed;
}
}
else //空中操作の処理(重力加速度等)
{
float tempy = moveDirection.y;
//(↓の2文の処理があると空中でも入力方向に動けるようになる)
//moveDirection = Vector3.Scale(targetDirection, new Vector3(1, 0, 1)).normalized;
//moveDirection *= speed;
moveDirection.y = tempy - gravity * Time.deltaTime;
}
//★走行アニメーション管理
if (v > .1 || v < -.1 || h > .1 || h < -.1) //(移動入力があると)
{
animator.SetFloat("Speed", 1f); //キャラ走行のアニメーションON
}
else //(移動入力が無いと)
{
animator.SetFloat("Speed", 0f); //キャラ走行のアニメーションOFF
}
}
void RotationControl() //キャラクターが移動方向を変えるときの処理
{
Vector3 rotateDirection = moveDirection;
rotateDirection.y = 0;
//それなりに移動方向が変化する場合のみ移動方向を変える
if (rotateDirection.sqrMagnitude > 0.01)
{
//緩やかに移動方向を変える
float step = rotateSpeed * Time.deltaTime;
Vector3 newDir = Vector3.Slerp(transform.forward, rotateDirection, step);
transform.rotation = Quaternion.LookRotation(newDir);
}
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag=="zepp")
{
SceneManager.LoadScene("zepp");
}
if (other.gameObject.tag == "Home")
{
SceneManager.LoadScene("Home");
this.transform.position = new Vector3(0, 5, 0);
}
}
void GetCamera()
{
if (myPV.isMine) //自キャラであれば実行
{
//MainCameraのtargetにこのゲームオブジェクトを設定
mainCam = Camera.main;
mainCam.GetComponent<CameraScript>().target = this.gameObject.transform;
}
}
/*void OnSceneLaoded(Scene scene, LoadSceneMode mode)
{
Debug.Log(" 123");
GetCamera();
}*/
}
PlayerUIScript.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class PlayerUIScript : MonoBehaviour
{
#region Public Properties
//キャラの頭上に乗るように調整するためのOffset
public Vector3 ScreenOffset = new Vector3(0f, 30f, 0f);
//プレイヤー名前設定用Text
public Text PlayerNameText;
//プレイヤーのHP用Slider
public Slider PlayerHPSlider;
//プレイヤーのチャット用Text
public Text PlayerChatText;
#endregion
#region Private Properties
//追従するキャラのPlayerManager情報
PlayerManager _target;
float _characterControllerHeight;
Transform _targetTransform;
Vector3 _targetPosition;
#endregion
#region MonoBehaviour Messages
void Awake()
{
//このオブジェクトはCanvasオブジェクトの子オブジェクトとして生成
this.GetComponent<Transform>().SetParent(GameObject.Find("Canvas").GetComponent<Transform>());
}
void Update()
{
//もしPlayerがいなくなったらこのオブジェクトも削除
if (_target == null)
{
Destroy(this.gameObject);
return;
}
// 現在のHPをSliderに適用
if (PlayerHPSlider != null)
{
PlayerHPSlider.value = _target.HP;
}
// 頭上チャットを表示
if (PlayerChatText != null)
{
PlayerChatText.text = _target.ChatText;
}
}
#endregion
void LateUpdate()
{
//targetのオブジェクトを追跡する
if (_targetTransform != null)
{
_targetPosition = _targetTransform.position; //三次元空間上のtargetの座標を得る
_targetPosition.y += _characterControllerHeight; //キャラクターの背の高さを考慮する
//targetの座標から頭上UIの画面上の二次元座標を計算して移動させる
this.transform.position = Camera.main.WorldToScreenPoint(_targetPosition) + ScreenOffset;
}
}
#region Public Methods
public void SetTarget(PlayerManager target)
{
if (target == null)//targetがいなければエラーをConsoleに表示
{
Debug.LogError("<Color=Red><a>Missing</a></Color> PlayMakerManager target for PlayerUI.SetTarget.", this);
return;
}
//targetの情報をこのスクリプト内で使うのでコピー
_target = target;
_targetTransform = _target.GetComponent<Transform>();
CharacterController _characterController = _target.GetComponent<CharacterController>();
//PlayerManagerの頭上UIに表示したいデータをコピー
if (_characterController != null)
{
_characterControllerHeight = _characterController.height;
}
if (PlayerNameText != null)
{
PlayerNameText.text = _target.photonView.owner.NickName;
}
if (PlayerHPSlider != null)
{
PlayerHPSlider.value = _target.HP;
}
if (PlayerChatText != null)
{
PlayerChatText.text = _target.ChatText;
}
}
#endregion
}
自分で試したこと
下記3つの方法を試してみました。
その①
ChatManagerが持つInRoomChat.csのStart関数内にDontDestroyOnLoad(gameobject);の一文を追加
[結果]
シーンAからシーンBに遷移後以下の状態
・ChatManagerは破壊されたとのログ出ず
・かつシーン遷移後のヒエラルキーにChatManagerオブジェクトは確認できない。
・画面上ではチャットUIは確認できない。
・エラーで固まる事はなく、プレイヤーの操作は可能な状態。
・また下記エラーがでます。
[エラーメッセージ]
PhotonView ID duplicate found: 1. New: View (0)1 on Canvas (scene) old: View (0)1 on ChatManager (scene). Maybe one wasn't destroyed on scene load?! Check for 'DontDestroyOnLoad'. Destroying old entry, adding new.
UnityEngine.Debug:LogError (object)
NetworkingPeer:RegisterPhotonView (PhotonView) (at Assets/BuyAsset/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:3684)
PhotonView:Awake () (at Assets/BuyAsset/Photon Unity Networking/Plugins/PhotonNetwork/PhotonView.cs:269)
プレイヤー操作可能な為、シーンBからシーンAに戻ると、以下の状態になります。
・チャットUIは画面上に表示され、チャット入力も可能。
・しかしプレイヤー頭上のはチャット文字が表示されない。
・ヒエラルキー上にChatMangerは存在している。
・プレイヤーの操作可能。
その②
ChatManagerに「シーン遷移で破棄されない&1つしか存在しない」スクリプト「DontDestroySingleObject」をアタッチ。
DontDestroySingleObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DontDestroySingleObject : MonoBehaviour
{
public static DontDestroySingleObject Instance
{
get; private set;
}
void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
Debug.Log( transform.name+"は破壊された");
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
Debug.Log(transform.name + "は存在する");
}
}
[結果]
シーンA開始と共にChatManagerオブジェクトが破壊され、同時にチャットUIも無くなる。
その③
ChatManagerを下記の状態に
・DontDestroySingleObject.csをアタッチ
・Resourceフォルダにいれプレハブ化
・ヒエラルキーから削除
・スタート時にGameManager.csからChatManagerを生成
[結果]
シーンA開始と共にChatManagerオブジェクトが破壊され、同時にチャットUIも無くなる。
/////////////////////////////////////////////////////////////////
冒頭に記載した各コードを元に上記①〜③を試しました。
失敗と共にコードや設定は冒頭の状態に戻しております。
////////////////////////////////////////////////////////////////
開発環境
max osX
Unity 2021.1.6f1
Pnoton1
PhotonChat
PhotonVoice1
UnityもQiitaも初心者のため、記載に不備などございましたら申し訳ございません。
ご指摘頂ければ修正、加筆させて頂きますので宜しくお願い致します。