今回はPHOTONを用いてマナトークンの所有権の移譲を行いたいと思います。
ついでに場所も動かしたいと考えています。
この山札の上に浮いているトークンをまとめて、ドローの際に移譲します。
それでは、まずPhotonのTransferOwnershipから内容を確認していきましょう。
UnityとPun2と C#でTransferOwnershipの準備をする
まず、TransferOwnershipをする前に、自分の山札の上のトークンを取得してしまいます。
”PreMana”タグとIsMineで自分の山札の上のトークンを全部取得する
preManaという名前の配列を最初から仕込んでおいて、その配列に全てのPreManaという名前のトークンを格納します。
この時点では、お互いのプレイヤーのPreManaトークンが配列に格納されます。
次に、PreManaトークンについているPhotonViewを取得して、そのIsMineの値がTrueの場合(自分のトークン)のみmyPreManaという別の配列に格納します。
これはfor文で回して全てのPreManaトークンについてその判別を行います。
これによって、自分のPreManaトークンだけを取得した配列が得られるという事になります。
この処理はドローするたびに行うので、配列がどんどん長くなっていかないように、配列を空にするスクリプトも足しておきました。
このトークン取得スクリプト部分だけをメソッド化して、ドローの際に呼んであげるようにしました。
参考までにメソッドの部分のスクリプトを貼っておきます。
public void FindPreManaTag ()
System. Array . Clear ( preMana, 0 , preMana. Length ) ;
System. Array . Clear ( myPreMana, 0 , myPreMana. Length ) ;
//"Premana"タグが付いているオブジェクトを配列で全取得
preMana = GameObject. FindGameObjectsWithTag ( "PreMana" ) ;
for ( int i = 0 ; i < preMana. Length ; i++ )
PhotonView c_PV = preMana [ i ] . GetComponent < PhotonView >() ;
//i番目の配列のPhotonViewが自分のである場合のみ
//自分のマナトークン配列にゲームオブジェクトを格納する
List < GameObject > list = new List < GameObject >( preMana ) ;
myPreMana = list. ToArray () ;
public void FindPreManaTag()
{
//配列を空にする
System.Array.Clear(preMana, 0, preMana.Length);
//配列を空にする
System.Array.Clear(myPreMana, 0, myPreMana.Length);
//"Premana"タグが付いているオブジェクトを配列で全取得
preMana = GameObject.FindGameObjectsWithTag("PreMana");
//配列の回数分下記の処理を繰り返す
for (int i = 0; i < preMana.Length; i++)
{
//i番目の配列のPhotonViewを取得する
PhotonView c_PV = preMana[i].GetComponent<PhotonView>();
//i番目の配列のPhotonViewが自分のである場合のみ
if (c_PV.IsMine)
{
//自分のマナトークン配列にゲームオブジェクトを格納する
List<GameObject> list = new List<GameObject>(preMana);
list.Add(preMana[i]);
myPreMana = list.ToArray();
}
}
}
public void FindPreManaTag()
{
//配列を空にする
System.Array.Clear(preMana, 0, preMana.Length);
//配列を空にする
System.Array.Clear(myPreMana, 0, myPreMana.Length);
//"Premana"タグが付いているオブジェクトを配列で全取得
preMana = GameObject.FindGameObjectsWithTag("PreMana");
//配列の回数分下記の処理を繰り返す
for (int i = 0; i < preMana.Length; i++)
{
//i番目の配列のPhotonViewを取得する
PhotonView c_PV = preMana[i].GetComponent<PhotonView>();
//i番目の配列のPhotonViewが自分のである場合のみ
if (c_PV.IsMine)
{
//自分のマナトークン配列にゲームオブジェクトを格納する
List<GameObject> list = new List<GameObject>(preMana);
list.Add(preMana[i]);
myPreMana = list.ToArray();
}
}
}
ドローする時に"myPreMana"を移動する
for文を使用して、manaPositionというTransformを取得しているオブジェクトの周りにランダムでトークンを配置します。
それから、浮いているトークンを置きたいのでRigidBodyを取得して、Rigidbodyの拘束を切ってしまいます。
コライダーも、次の所有権の移譲の際に使うのでオンにします。
ここで、myPreMana配列から移動したGameObjectを消去するのですが、0番目を消去すると、配列がズレてしまうので、
後ろから削除していくことを考え、for文を後ろからディクリメントで回しています。
それによって正しい順番で処理が行われるようにしています。
この部分もメソッド化して、ドローの際に呼ばれるようにしています。
参考までにスクリプト(メソッド部)を貼っておきます。
for ( int i = myPreMana. Length - 1 ; i > = 0 ; i-- )
float x = manaPosition. transform . position . x - 0.025 f + Random. Range ( 0 , 0.05 f ) ;
float y = manaPosition. transform . position . y + Random. Range ( 0 , 0.05 f ) ;
float z = manaPosition. transform . position . z - 0.025 f + Random. Range ( 0 , 0.05 f ) ;
Rigidbody RB = myPreMana [ i ] . GetComponent < Rigidbody >() ;
RB. constraints = RigidbodyConstraints. None ;
myPreMana [ i ] . GetComponent < BoxCollider >() . enabled = true ;
myPreMana [ i ] . transform . position = new Vector3 ( x, y, z ) ;
var list = new List < GameObject >() ;
list. AddRange ( myPreMana ) ;
list. Remove ( myPreMana [ i ]) ;
myPreMana = list. ToArray () ;
void TransferOwnership()
{
for (int i = myPreMana.Length-1; i >= 0; i--)
{
float x = manaPosition.transform.position.x - 0.025f + Random.Range(0, 0.05f);
float y = manaPosition.transform.position.y + Random.Range(0, 0.05f);
float z = manaPosition.transform.position.z - 0.025f + Random.Range(0, 0.05f);
Rigidbody RB = myPreMana[i].GetComponent<Rigidbody>();
RB.constraints = RigidbodyConstraints.None;
myPreMana[i].GetComponent<BoxCollider>().enabled = true;
myPreMana[i].transform.position = new Vector3(x, y, z);
var list = new List<GameObject>();
list.AddRange(myPreMana);
list.Remove(myPreMana[i]);
myPreMana = list.ToArray();
}
}
void TransferOwnership()
{
for (int i = myPreMana.Length-1; i >= 0; i--)
{
float x = manaPosition.transform.position.x - 0.025f + Random.Range(0, 0.05f);
float y = manaPosition.transform.position.y + Random.Range(0, 0.05f);
float z = manaPosition.transform.position.z - 0.025f + Random.Range(0, 0.05f);
Rigidbody RB = myPreMana[i].GetComponent<Rigidbody>();
RB.constraints = RigidbodyConstraints.None;
myPreMana[i].GetComponent<BoxCollider>().enabled = true;
myPreMana[i].transform.position = new Vector3(x, y, z);
var list = new List<GameObject>();
list.AddRange(myPreMana);
list.Remove(myPreMana[i]);
myPreMana = list.ToArray();
}
}
UnityとPun2と C#でTransferOwnershipの準備をする
先ほど移動されてきたトークンの所有権の取得を行います。
TransferOwnershipを使おうとしたのですが、PUN2ではRequestOwnershipを使うようなので、そちらで実装しました。
ここのサイトを参考 に書いています。
プレートとの衝突判定でトークンのRequestOwnershipを発動させる
RequestOwnershipのトリガーとしては、ちょっとダサいのですが、衝突判定でRequestOwnershipを発動しようと思い、
まずは、四角い石のプレートを用意して、コライダーとRigidbodyをつけました。
今回はCollisionEnterを使いたかったので、IsTriggerはオフです。
プレートに重力で落ちてきた”Mana”というタグの付いたトークンがぶつかったら、所有権の移動を行うという書き方にしています。
そして、所有権が置き換わったら、”Cost”にタグを付け替えて、カードのマナコストに使用できるようにしています。
RequestOwnershipを発動しただけで、所有権を取れるのは、PhotonViewの設定をTakeOverにしているからです。
これがFixedだと所有権は取れませんし、Requestであれば、コールバックメソッドが必要になるようです。
四角いプレートに付けたスクリプトを下に記載しておきます。
using System.Collections;
using System.Collections.Generic;
public class ManaCollider : MonoBehaviour
// Start is called before the first frame update
private void OnCollisionEnter ( Collision collision )
Debug. Log ( "triggerenter" ) ;
if ( collision. gameObject . tag == "Mana" )
collision. gameObject . GetComponent < PhotonView >() . RequestOwnership () ;
collision. gameObject . tag = "Cost" ;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class ManaCollider : MonoBehaviour
{
// Start is called before the first frame update
private void OnCollisionEnter(Collision collision)
{
Debug.Log("triggerenter");
if(collision.gameObject.tag == "Mana")
{
Debug.Log("cost");
collision.gameObject.GetComponent<PhotonView>().RequestOwnership();
collision.gameObject.tag = "Cost";
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class ManaCollider : MonoBehaviour
{
// Start is called before the first frame update
private void OnCollisionEnter(Collision collision)
{
Debug.Log("triggerenter");
if(collision.gameObject.tag == "Mana")
{
Debug.Log("cost");
collision.gameObject.GetComponent<PhotonView>().RequestOwnership();
collision.gameObject.tag = "Cost";
}
}
}
UnityとPun2と C#でRequestOwnershipまとめ
元祖のPhotonClassicでは、TransferOwnershipを用いて所有権を移譲していましたが、PUN2では、一通り調べてはみたものの、RequestOwnershipでやるようになっているようです。
TransferOwnershipを使用した場合は、player.IDという値が使えましたが、PUN2では見当たりませんでした。
よってPhotonViewのRequestOwnershipを使うのが良いように思います。
今回はまずタグを用いて山札からカードをドローする際にトークンを取得、移動して、それからColliderを用いてRequestOwnershipを行いました。
参考までに、山札につけたスクリプトも貼っておきます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems;
public class CardDraw : MonoBehaviour
[ SerializeField ] TextMeshPro libraryNum;
public GameObject [] myPreMana;
public GameObject [] preMana;
[ SerializeField ] Transform libraryPosition;
[ SerializeField ] Transform manaPosition;
CardSwitcher cardSwitcher;
Transform pos = transform;
float x = pos. transform . position . x ;
float y = pos. transform . position . y ;
float z = pos. transform . position . z ;
cardSpawnPoint = new Vector3 ( x, y + 0.02 f, z - 0.1 f ) ;
libraryNum. text = System. Convert . ToString ( library. Length ) ;
GameObject cardCopy = ( GameObject ) PhotonNetwork. Instantiate ( "NewCard" , cardSpawnPoint, Quaternion. Euler ( 0 , 0 , 0 ) , 0 ) ;
//山札の配列からランダムで一つ要素のインデックスを選ぶ
int randomDraw = Random. Range ( 0 , library. Length ) ;
cardSwitcher = cardCopy. GetComponent < CardSwitcher >() ;
cardSwitcher. SwitchMaterial ( library [ randomDraw ]) ;
Rigidbody rigidbody = cardCopy. AddComponent < Rigidbody >() ;
rigidbody. transform . rotation = Quaternion. Euler ( 0 , 0 , 0 ) ;
rigidbody. constraints = RigidbodyConstraints. FreezeRotation ;
var list = new List < int >() ;
list. Remove ( library [ randomDraw ]) ;
library = list. ToArray () ;
libraryNum. text = System. Convert . ToString ( library. Length ) ;
GameObject libraryMesh = GameObject. Find ( "card" ) ;
libraryMesh. transform . localScale = new Vector3 ( 1 , library. Length , 1 ) ;
public void FindPreManaTag ()
System. Array . Clear ( preMana, 0 , preMana. Length ) ;
System. Array . Clear ( myPreMana, 0 , myPreMana. Length ) ;
//"Premana"タグが付いているオブジェクトを配列で全取得
preMana = GameObject. FindGameObjectsWithTag ( "PreMana" ) ;
for ( int i = 0 ; i < preMana. Length ; i++ )
PhotonView c_PV = preMana [ i ] . GetComponent < PhotonView >() ;
//i番目の配列のPhotonViewが自分のである場合のみ
//自分のマナトークン配列にゲームオブジェクトを格納する
List < GameObject > list = new List < GameObject >( preMana ) ;
myPreMana = list. ToArray () ;
for ( int i = myPreMana. Length - 1 ; i > = 0 ; i-- )
float x = manaPosition. transform . position . x - 0.025 f + Random. Range ( 0 , 0.05 f ) ;
float y = manaPosition. transform . position . y + Random. Range ( 0 , 0.05 f ) ;
float z = manaPosition. transform . position . z - 0.025 f + Random. Range ( 0 , 0.05 f ) ;
Rigidbody RB = myPreMana [ i ] . GetComponent < Rigidbody >() ;
RB. constraints = RigidbodyConstraints. None ;
myPreMana [ i ] . GetComponent < BoxCollider >() . enabled = true ;
myPreMana [ i ] . transform . position = new Vector3 ( x, y, z ) ;
var list = new List < GameObject >() ;
list. AddRange ( myPreMana ) ;
list. Remove ( myPreMana [ i ]) ;
myPreMana = list. ToArray () ;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using TMPro;
public class CardDraw : MonoBehaviour
{
public int[] library;
[SerializeField] TextMeshPro libraryNum;
public GameObject[] myPreMana;
public GameObject[] preMana;
Vector3 cardSpawnPoint;
[SerializeField] Transform libraryPosition;
[SerializeField] Transform manaPosition;
CardSwitcher cardSwitcher;
int playerID;
private void Awake()
{
//現在のポジションをposに格納
Transform pos = transform;
//posのpositionをフロートで取得
float x = pos.transform.position.x;
float y = pos.transform.position.y;
float z = pos.transform.position.z;
//取得したフロートに位置をずらす分足す
cardSpawnPoint = new Vector3(x, y + 0.02f, z - 0.1f);
}
private void Start()
{
libraryNum.text = System.Convert.ToString(library.Length);
}
public void click()
{
Draw();
FindPreManaTag();
TransferOwnership();
}
public void Draw()
{
//カードをインスタンス化する
GameObject cardCopy = (GameObject)PhotonNetwork.Instantiate("NewCard", cardSpawnPoint, Quaternion.Euler(0, 0, 0), 0);
//山札の配列からランダムで一つ要素のインデックスを選ぶ
int randomDraw = Random.Range(0, library.Length);
//選んだ山札カードのマテリアルをあてがう
cardSwitcher = cardCopy.GetComponent<CardSwitcher>();
cardSwitcher.SwitchMaterial(library[randomDraw]);
//rigidbody有効化
Rigidbody rigidbody = cardCopy.AddComponent<Rigidbody>();
rigidbody.transform.rotation = Quaternion.Euler(0, 0, 0);
rigidbody.constraints = RigidbodyConstraints.FreezeRotation;
//山札配列からその要素を一つ削除
var list = new List<int>();
list.AddRange(library);
list.Remove(library[randomDraw]);
library = list.ToArray();
libraryNum.text = System.Convert.ToString(library.Length);
//山札のサイズを変化させる
if (library.Length == 0)
{
Destroy(gameObject);
}
else
{
GameObject libraryMesh = GameObject.Find("card");
libraryMesh.transform.localScale = new Vector3(1, library.Length, 1);
}
}
public void FindPreManaTag()
{
//配列を空にする
System.Array.Clear(preMana, 0, preMana.Length);
//配列を空にする
System.Array.Clear(myPreMana, 0, myPreMana.Length);
//"Premana"タグが付いているオブジェクトを配列で全取得
preMana = GameObject.FindGameObjectsWithTag("PreMana");
//配列の回数分下記の処理を繰り返す
for (int i = 0; i < preMana.Length; i++)
{
//i番目の配列のPhotonViewを取得する
PhotonView c_PV = preMana[i].GetComponent<PhotonView>();
//i番目の配列のPhotonViewが自分のである場合のみ
if (c_PV.IsMine)
{
//自分のマナトークン配列にゲームオブジェクトを格納する
List<GameObject> list = new List<GameObject>(preMana);
list.Add(preMana[i]);
myPreMana = list.ToArray();
}
}
}
void TransferOwnership()
{
for (int i = myPreMana.Length-1; i >= 0; i--)
{
float x = manaPosition.transform.position.x - 0.025f + Random.Range(0, 0.05f);
float y = manaPosition.transform.position.y + Random.Range(0, 0.05f);
float z = manaPosition.transform.position.z - 0.025f + Random.Range(0, 0.05f);
Rigidbody RB = myPreMana[i].GetComponent<Rigidbody>();
RB.constraints = RigidbodyConstraints.None;
myPreMana[i].GetComponent<BoxCollider>().enabled = true;
myPreMana[i].transform.position = new Vector3(x, y, z);
var list = new List<GameObject>();
list.AddRange(myPreMana);
list.Remove(myPreMana[i]);
myPreMana = list.ToArray();
}
}
}
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using TMPro;
public class CardDraw : MonoBehaviour
{
public int[] library;
[SerializeField] TextMeshPro libraryNum;
public GameObject[] myPreMana;
public GameObject[] preMana;
Vector3 cardSpawnPoint;
[SerializeField] Transform libraryPosition;
[SerializeField] Transform manaPosition;
CardSwitcher cardSwitcher;
int playerID;
private void Awake()
{
//現在のポジションをposに格納
Transform pos = transform;
//posのpositionをフロートで取得
float x = pos.transform.position.x;
float y = pos.transform.position.y;
float z = pos.transform.position.z;
//取得したフロートに位置をずらす分足す
cardSpawnPoint = new Vector3(x, y + 0.02f, z - 0.1f);
}
private void Start()
{
libraryNum.text = System.Convert.ToString(library.Length);
}
public void click()
{
Draw();
FindPreManaTag();
TransferOwnership();
}
public void Draw()
{
//カードをインスタンス化する
GameObject cardCopy = (GameObject)PhotonNetwork.Instantiate("NewCard", cardSpawnPoint, Quaternion.Euler(0, 0, 0), 0);
//山札の配列からランダムで一つ要素のインデックスを選ぶ
int randomDraw = Random.Range(0, library.Length);
//選んだ山札カードのマテリアルをあてがう
cardSwitcher = cardCopy.GetComponent<CardSwitcher>();
cardSwitcher.SwitchMaterial(library[randomDraw]);
//rigidbody有効化
Rigidbody rigidbody = cardCopy.AddComponent<Rigidbody>();
rigidbody.transform.rotation = Quaternion.Euler(0, 0, 0);
rigidbody.constraints = RigidbodyConstraints.FreezeRotation;
//山札配列からその要素を一つ削除
var list = new List<int>();
list.AddRange(library);
list.Remove(library[randomDraw]);
library = list.ToArray();
libraryNum.text = System.Convert.ToString(library.Length);
//山札のサイズを変化させる
if (library.Length == 0)
{
Destroy(gameObject);
}
else
{
GameObject libraryMesh = GameObject.Find("card");
libraryMesh.transform.localScale = new Vector3(1, library.Length, 1);
}
}
public void FindPreManaTag()
{
//配列を空にする
System.Array.Clear(preMana, 0, preMana.Length);
//配列を空にする
System.Array.Clear(myPreMana, 0, myPreMana.Length);
//"Premana"タグが付いているオブジェクトを配列で全取得
preMana = GameObject.FindGameObjectsWithTag("PreMana");
//配列の回数分下記の処理を繰り返す
for (int i = 0; i < preMana.Length; i++)
{
//i番目の配列のPhotonViewを取得する
PhotonView c_PV = preMana[i].GetComponent<PhotonView>();
//i番目の配列のPhotonViewが自分のである場合のみ
if (c_PV.IsMine)
{
//自分のマナトークン配列にゲームオブジェクトを格納する
List<GameObject> list = new List<GameObject>(preMana);
list.Add(preMana[i]);
myPreMana = list.ToArray();
}
}
}
void TransferOwnership()
{
for (int i = myPreMana.Length-1; i >= 0; i--)
{
float x = manaPosition.transform.position.x - 0.025f + Random.Range(0, 0.05f);
float y = manaPosition.transform.position.y + Random.Range(0, 0.05f);
float z = manaPosition.transform.position.z - 0.025f + Random.Range(0, 0.05f);
Rigidbody RB = myPreMana[i].GetComponent<Rigidbody>();
RB.constraints = RigidbodyConstraints.None;
myPreMana[i].GetComponent<BoxCollider>().enabled = true;
myPreMana[i].transform.position = new Vector3(x, y, z);
var list = new List<GameObject>();
list.AddRange(myPreMana);
list.Remove(myPreMana[i]);
myPreMana = list.ToArray();
}
}
}
おわりに
今回はPhotonClassicとPUN2でOwnershipの扱いが違うことに躓きました。
それから、配列をfor文消去する際に、配列がズレる等、バグフィックスに時間がかかり、
なかなかタフな一日でした。
原因不明のバグに当たったら、まずそのバグを人に説明できるくらい、明確で再現性のある現象として整理することで、解決の糸口がつかめる事が多いと感じました。
実際に人に聞くかどうかは別として、そのくらい現状の理解が進むとバグも自然と解きやすいようにおもいます。
今回は大変でしたが、これからも頑張っていきたいと思います。
それでは、また!!