【Unity】PUN2を学習する　ゲームを作る-3

この記事は、[【完走したい】楽しくいろいろやる Advent Calendar 2023](https://qiita.com/advent-calendar/2023/notbeginner)の9日目です。
全部書いておきます。気を付けることも書きました。結構あります。
# 結果
↓全てのコード
接続用
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UIElements;

public class PhotonAdokare : MonoBehaviourPunCallbacks
{
    [Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]
    [SerializeField]
    private byte maxPlayersPerRoom = 4;
    //普段は絶対１にする
    string gameVersion = "1";

    // Start is called before the first frame update
    void Awake()
    {
        PhotonNetwork.AutomaticallySyncScene = true;   
    }

    void Start()
    {
        PhotonNetwork.NickName = "Player";
        //ゲーム開始時に自動的に接続する時に使う。
          Connect();

        }
    public void Connect()
    {//Photonのネットワークに接続したら
        if (PhotonNetwork.IsConnected)
        {
            PhotonNetwork.JoinRandomRoom();

        }
        //そうでなければ、
        else
        {
            PhotonNetwork.GameVersion = gameVersion;
            PhotonNetwork.ConnectUsingSettings();
        }
    }
    public override void OnConnectedToMaster()
    {//マスターサーバーへ接続したとき
        Debug.Log("Pun Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");
        PhotonNetwork.JoinRandomRoom();
    }
    public override void OnDisconnected(DisconnectCause cause)
    {//切断したとき
      Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);
    }
    public override void OnJoinRandomFailed(short returnCode, string message)
    {//ルームへの参加に失敗したとき（ランダム）
        Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");

        // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
        PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = maxPlayersPerRoom });
    }

    public override void OnJoinedRoom()
    {//ルームに入った時
        Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
        PhotonNetwork.Instantiate("Cube", new Vector3(0, 0, 0), Quaternion.identity,0);
      





    }
}

```
自分で動かすやつ制御
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using Unity.VisualScripting;

public class TFControll : MonoBehaviourPunCallbacks
{ public bool Once = false;
    public int A;
    // Start is called before the first frame update
    void Start()
    {
       
        if (photonView.IsMine)
        {

            if (photonView.OwnerActorNr == 1)
            {
                this.gameObject.transform.position = new Vector3(0, 0, -4);
                A = 1;
            }
            else
            {
                this.gameObject.transform.position = new Vector3(0, 0, 4);
                A = 2;
            }

        }

    }
    // Update is called once per frame
    void Update()
    {
        if (photonView.IsMine)
        {
          
            if (Input.GetKey(KeyCode.LeftArrow))
            {
                if (this.gameObject.transform.position.x > -4)
                {
                    this.transform.Translate(-0.3f, 0.0f, 0);

                }

            }

            if (Input.GetKey(KeyCode.RightArrow))
            {
                if (this.gameObject.transform.position.x < 4)
                {

                    this.transform.Translate(0.3f, 0.0f, 0);
                }

            }
        }





                

    }
       
    
   
}
```
ボール制御
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using Unity.VisualScripting;
using static Unity.VisualScripting.Member;

public class BallControll : MonoBehaviourPunCallbacks
{private Rigidbody rb;
    public bool Once = false;
    public int BallRotate;
    // Start is called before the first frame update
    void Start()
    {
    
     

    }

    // Update is called once per frame
    void Update()
    {
        if (PhotonNetwork.InRoom)
        {
           
            if (PhotonNetwork.CurrentRoom.PlayerCount >= 2)
            {
                if (photonView.OwnerActorNr == 0)
                {
                    if (Once == false)
                    {
                        Once = true;
                        Debug.Log("Game Start");
                        rb = this.GetComponent<Rigidbody>();
                        BallRotate = Random.Range(0, 360);
                        Transform myTransform = this.transform;
                        Vector3 localAngle = myTransform.localEulerAngles;
                        localAngle.y = BallRotate;
                        myTransform.localEulerAngles = localAngle;
                        Vector3 Kasoku = new Vector3(4f, 0, 4f);
                        rb.AddForce(Kasoku, ForceMode.VelocityChange);

                    }

                }
            }
        }
    }
    
}
```
判断用
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class Judgement : MonoBehaviourPunCallbacks
{
    int id;
    public bool Test1;
    public bool Test2;
  
    public int Test3;
    public GameObject Win;
    public GameObject Lose;
    private GameObject Cube;
    // Start is called before the first frame update
    void Start()
    {
      
        Test1 = false;
        Test2 = false;
        Win.SetActive(false);
        Lose.SetActive(false);
       
    }

    // Update is called once per frame
    void Update()
    {

       
    }
    public void OnCollisionEnter(Collision collision)
    {

        if (collision.gameObject.name == "JudA")
        {
            GameObject GamePlayer = GameObject.Find("Cube(Clone)");
            id = GamePlayer.GetComponent<TFControll>().A;
            if (id == 1)
            {
                Debug.Log("1");
            }
            if (id == 2)
            {
                Debug.Log("2");
            }
            if (id == 1)
            {
                Lose.SetActive(true);

            }
            if (id == 2)
            {
                Win.SetActive(true);
            }

            if (photonView.IsMine)
            {
                Invoke("GameFin", 2f);
            }
        }
        if (collision.gameObject.name == "JudB")
        {
            Cube = GameObject.Find("Cube(Clone)");
            id = Cube.GetComponent<TFControll>().A;
            if (id == 1)
            {
                Debug.Log("1");
            }
            if (id == 2)
            {
                Debug.Log("2");
            }
            if (id == 1)
            {
                Win.SetActive(true);


            }
            if (id == 2)
            {
                Lose.SetActive(true);
            }

            if (photonView.IsMine)
            {//通信環境が悪いなどで結果が出なかったときはもっと大きい数字にしてください。上も同様
                Invoke("GameFin", 2f);
            }
        }





    }




    public void GameFin()
    {
        if (photonView.IsMine)
        {
            Player owner = (Player)photonView.Owner;
            PhotonNetwork.Destroy(this.gameObject);
        }

    }
}
```
オブジェクト同期用
[こちら](https://zenn.dev/o8que/books/bdcb9af27bdd7d/viewer/272c0d)の記事より抜粋
```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
public class ObjSync : MonoBehaviourPunCallbacks, IPunObservable
{
    void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            // Transformの値をストリームに書き込んで送信する
            stream.SendNext(transform.localPosition);
            stream.SendNext(transform.localRotation);
            stream.SendNext(transform.localScale);
        }
        else
        {
            // 受信したストリームを読み込んでTransformの値を更新する
            transform.localPosition = (Vector3)stream.ReceiveNext();
            transform.localRotation = (Quaternion)stream.ReceiveNext();
            transform.localScale = (Vector3)stream.ReceiveNext();
        }
    }
}
```

コードはこんなところです。
# 注意すべきこと

・プレイヤーで共通のオブジェクト
Photon Transform viewとPhoton Viewをつける

:::note warn
prefab化するが、Hierarchyから消してはいけない。
:::

・プレイヤー自身
Photon Transform viewとPhoton Viewをつける

:::note warn
prefab化して、Hierarchyから消す。
Instantiateを使って生成する。
:::

・初期位置を任意で決めたい
おそらく無理です。(0,0,0)の位置になってしまいました。
<b>しかし、そのオブジェクトにアタッチした別のスクリプトのvoid Start内から場所を変更することであたかも初期位置がそこであったように見せかけることは可能です。</b>

・ただのオブジェクトがプレイヤーの番号を参照するとき

生成したオブジェクトをFind関数を使って見つけ（名前にcloneが付くことに注意）、その中の
photonView.OwnerActorNrを参照する。
InstantiateされたオブジェクトでないとphotonView.OwnerActorNrが機能しない。prefab化されたオブジェクトにくっつけたスクリプトは、Hierarchyのオブジェクトを参照できない（Type mismatchと出ました）。

わかりずらそうなので、例をつけておきます。
例）
A⇒prefabに入っているキャラクターのオブジェクト、これから生成される。photonView.OwnerActorNrが使える。

B⇒Instantiateされていないし、photon viewもphoton Transform viewもくっついていないオブジェクト

があったとします。
<b>この時（実行する前の段階）は、AからBのオブジェクトを参照できず、BからもAのオブジェクトを参照することはできません。</b>
しかし実行してAが生成、Hierarchyにクローンが作成されると
BはFind関数を用いてAを参照できるようになり、photonView.OwnerActorNrの数値を取ってくることができるわけです。

これが使われているのが今回の判断用のコードです。

# 結局どんなものができたか
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">qiitaアドカレ用　pun2　通信環境：悪 <a href="https://t.co/sAwJahaS80">pic.twitter.com/sAwJahaS80</a></p>&mdash; ある暇な人 (@Aruhimanahito) <a href="https://twitter.com/Aruhimanahito/status/1733359710429229412?ref_src=twsrc%5Etfw">December 9, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# まとめ
結構大変でした。
でも、pun2の使いかたを知るいいきっかけになりました。