//tips
//PUN2エラー検証
下記のエラーの検証を行なっていく。
RPC method 'FireProjectile' found on object with PhotonView 1001 but has wrong parameters. Implement as 'FireProjectile(Single)'. PhotonMessageInfo is optional as final parameter.
UnityEngine.Debug:LogErrorFormat(Object, String, Object[])
Photon.Pun.PhotonNetwork:ExecuteRpc(Hashtable, Player) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:632)
Photon.Pun.PhotonNetwork:OnEvent(EventData) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:2158)
Photon.Realtime.LoadBalancingClient:OnEvent(EventData) (at Assets/Photon/PhotonRealtime/Code/LoadBalancingClient.cs:3157)
ExitGames.Client.Photon.PeerBase:DeserializeMessageAndCallback(StreamBuffer) (at D:/Dev/Work/photon-dotnet-sdk/PhotonDotNet/PeerBase.cs:659)
ExitGames.Client.Photon.EnetPeer:DispatchIncomingCommands() (at D:/Dev/Work/photon-dotnet-sdk/PhotonDotNet/EnetPeer.cs:597)
ExitGames.Client.Photon.PhotonPeer:DispatchIncomingCommands() (at D:/Dev/Work/photon-dotnet-sdk/PhotonDotNet/PhotonPeer.cs:1845)
Photon.Pun.PhotonHandler:Dispatch() (at Assets/Photon/PhotonUnityNetworking/Code/PhotonHandler.cs:208)
Photon.Pun.PhotonHandler:FixedUpdate() (at Assets/Photon/PhotonUnityNetworking/Code/PhotonHandler.cs:142)
該当してそうなコードはGamePlayerのスクリプト、projectileのスクリプト、projectilemanagerのスクリプト箇所は下記となる。
//GamePlayerのスクリプト
// [PunRPC]属性をつけると、RPCでの実行が有効になる
[PunRPC]
private void FireProjectile(Vector3 origin, float angle, PhotonMessageInfo info)
{
int timestamp = info.SentServerTimestamp;
projectileManager.Fire(timestamp, photonView.OwnerActorNr, origin, angle, timestamp);
}
//projectileのスクリプト
public class Projectile : MonoBehaviour
{
private Vector3 origin; // 弾を発射した時刻での座標
private Vector3 velocity;
private int timestamp; // 弾を発射した時刻
public int Id { get; private set; } // 弾のID
public int OwnerId { get; private set; } // 弾を発射したプレイヤーのID
public bool Equals(int id, int ownerId) => id == Id && ownerId == OwnerId;
public bool IsActive => gameObject.activeSelf;
public void Activate(int id, int ownerId, Vector3 origin, float angle, int timestamp)
{ // メソッド名変更
{
Id = id;
OwnerId = ownerId;
this.origin = origin;
velocity = 9f * new Vector3(Mathf.Cos(angle), Mathf.Sin(angle));
this.timestamp = timestamp;
OnUpdate(); // transform.positionの初期値を決めるため、一度更新する
gameObject.SetActive(true);
}
}
//projectilemanagerのスクリプト
// 弾を発射(アクティブ化)するメソッド
public void Fire(int id, int ownerId, Vector3 origin, float angle ,int timestamp)
{
// 非アクティブの弾があれば使い回す、なければ生成する
var projectile = (inactivePool.Count > 0)
? inactivePool.Pop()
: Instantiate(projectilePrefab, transform);
projectile.Activate(id, ownerId, origin, angle, timestamp);
activeList.Add(projectile);
}
球のidパラメーターと時間をidにする方法が噛み合っていないのではないかと考え、コードを確認していく。
GamePlayerのPhotonView.Owner.ActorNumber(PhotonView.OwnerActorNr)から、ネットワークオブジェクトを生成したプレイヤーのIDを取得することができるので、それを使っている。
PhotonではPhotonNetwork.ServerTimestampから、現在のサーバー時刻を取得でき、info.SentServerTimestampで時刻をtimestampとして取得している。
変数の相互参照はうまくいっているように思われるので、timestampがきちんと撮れているのかをDebugで確認する。
[PunRPC]
private void FireProjectile(Vector3 origin, float angle, PhotonMessageInfo info)
{
int timestamp = info.SentServerTimestamp;
projectileManager.Fire(timestamp, photonView.OwnerActorNr, origin, angle, timestamp);
Debug.Log("Time"+timestamp);
Debug.Log("Origin"+origin);
Debug.Log("Info"+info);
}
このように確認したところ一応それぞれの値は返ってきた。
Time1378413571
UnityEngine.Debug:Log(Object)
GamePlayer:FireProjectile(Vector3, Single, PhotonMessageInfo) (at Assets/Script/GamePlayer.cs:75)
System.Reflection.MethodBase:Invoke(Object, Object[])
Photon.Pun.PhotonNetwork:ExecuteRpc(Hashtable, Player) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:564)
Photon.Pun.PhotonNetwork:RPC(PhotonView, String, RpcTarget, Player, Boolean, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:1222)
Photon.Pun.PhotonNetwork:RPC(PhotonView, String, RpcTarget, Boolean, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetwork.cs:2812)
Photon.Pun.PhotonView:RPC(String, RpcTarget, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonView.cs:792)
GamePlayer:Update() (at Assets/Script/GamePlayer.cs:62)
Origin(3.0, 0.4, 0.0)
UnityEngine.Debug:Log(Object)
GamePlayer:FireProjectile(Vector3, Single, PhotonMessageInfo) (at Assets/Script/GamePlayer.cs:76)
System.Reflection.MethodBase:Invoke(Object, Object[])
Photon.Pun.PhotonNetwork:ExecuteRpc(Hashtable, Player) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:564)
Photon.Pun.PhotonNetwork:RPC(PhotonView, String, RpcTarget, Player, Boolean, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:1222)
Photon.Pun.PhotonNetwork:RPC(PhotonView, String, RpcTarget, Boolean, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetwork.cs:2812)
Photon.Pun.PhotonView:RPC(String, RpcTarget, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonView.cs:792)
GamePlayer:Update() (at Assets/Script/GamePlayer.cs:62)
Info[PhotonMessageInfo: Sender='#01 'Player'' Senttime=1378413.571]
UnityEngine.Debug:Log(Object)
GamePlayer:FireProjectile(Vector3, Single, PhotonMessageInfo) (at Assets/Script/GamePlayer.cs:77)
System.Reflection.MethodBase:Invoke(Object, Object[])
Photon.Pun.PhotonNetwork:ExecuteRpc(Hashtable, Player) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:564)
Photon.Pun.PhotonNetwork:RPC(PhotonView, String, RpcTarget, Player, Boolean, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetworkPart.cs:1222)
Photon.Pun.PhotonNetwork:RPC(PhotonView, String, RpcTarget, Boolean, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetwork.cs:2812)
Photon.Pun.PhotonView:RPC(String, RpcTarget, Object[]) (at Assets/Photon/PhotonUnityNetworking/Code/PhotonView.cs:792)
GamePlayer:Update() (at Assets/Script/GamePlayer.cs:62)
これを見るとinfoからtimestampを抽出できていること、originの座標が取得できていることがわかる。
もしかしたら連続でタッチした場合に球のIDが分けられない状況が発生しているのでエラーにFireProjectile(Single)となっているのではないかと考え、今度は2回連続で球を放出する。Debug.Log("Owner" + photonView.OwnerActorNr);も追加した。
2度クリックしても同じエラーメッセージが2回でており、内容もおかしいところはないように思われる。
Time1828040584
Origin(2.2, -1.9, 0.0)
Info[PhotonMessageInfo: Sender='#01 'Player'' Senttime=1828040.584]
Owner1
Time1828041261
Origin(2.2, -1.9, 0.0)
Info[PhotonMessageInfo: Sender='#01 'Player'' Senttime=1828041.261]
Owner1
PunRPCの一回ごとの呼び出し処理の部分でエラーが生じているように思われる。
Implement as 'FireProjectile(Single)'. PhotonMessageInfo is optional as final parameter.は、infoで取得する値に対して、timestampのintが表示できる値に制限があることで表示されているのではないかと思っていたが、GamePlayerスクリプトのGamePlayer:Update() の項目の下記二つが重複しており、上のスクリプトが不要で削除したらエラーが治った。
// FireProjectile(angle)をRPCで実行する
//photonView.RPC(nameof(FireProjectile), RpcTarget.All, angle);
// 弾を発射するたびに弾のIDを1ずつ増やしていく
photonView.RPC(nameof(FireProjectile), RpcTarget.All, transform.position, angle);
エラー文の一番下に記載されているGamePlayer:Update() (at Assets/Script/GamePlayer.cs:62)にもう少し気を配るべきであったと反省。エラー表示で自分のスクリプトに関係がある部分は再度検証する癖をつける。
//クラスのメンバ変数の後の {get; set;} の記述
一人で開発していると{get; set;}はコードを見づらくするように思えるが、組織で動く際には、変数の取得に制限を設けたり、変数がどのように編集されているかなどを見直す印になるので使われるのだということを理解した。
今後は、publicで書いていた部分も{get; set;}などで相互編集することを前提に記載する練習もしたほうがいいかもしれない。
下記参考:
https://teratail.com/questions/30083
https://qiita.com/wakka0014/items/a3cd3a1a9a48de6beaee
//C#のラムダ式【=>】
コードに時々出現する=>の意味を調べた。
例えば、直近では下記のコードを使っている。
public bool Equals(int id, int ownerId) => id == Id && ownerId == OwnerId;
ラムダ式の使い方として、左辺には引数を列挙したもの、右辺には式の処理の実装が記述される。
(引数1, 引数2, ...) => [処理コードの実装]
多くの場合、ラムダ式を参照するデリゲートに代入するため、下記の書式が利用される。
デリゲート変数 = (引数1, 引数2, ...) => [処理コードの実装]
デリゲートを簡単にいうと、関数を変数のように扱うもので、これにより関数の引数に変数とした関数を渡す事ができる。関数の引数に関数を取ることができる。
Delegate型の変数を宣言する方法は
delegate 戻り値の型 デリゲート型名(引数リスト);
このように関数に必要な「戻り値」と「引数」が表されている。
例えば、標準出力にHelloと表示するだけのような、戻り値なし、引数無しのSayHelloというデリゲート型名を宣言するには
delegate void SayHello();
名前を引数に入れ、出力に加えると
delegate string SayHello(string name);
のような 形で表される。
これを簡単なコードで表現してみると、
class Program
{
// まず、delegate型の変数を宣言
// hugeという文字列を表示するので、void
public delegate void Delegate(); // (1)
static void Main(string[] args)
{
// hugeという変数を(1)で定義したDelegate型で定義
// hugeに、(2)で定義したHuge関数代入
Delegate huge = Huge;
//実際には関数が代入されているので、Huge関数が実行される
huge();
Console.ReadLine();
}
static void Huge() { // (2)
Console.WriteLine("huge");
}
引数と戻り値のあるDelegateの例も記載。
class Program
{
// 引数には、長さをカウントしたい対象の文字列、戻り値には文字列の長さの数字を返す
// 引数はstring、戻り値はint型
public delegate int Delegate(string s); // (1)
static void Main(string[] args)
{
// (1)で定義したDelegate型の変数countString
// 変数countStirngに、関数CountStringを代入
Delegate countString = CountString;
// 変数countStringを実行すると、関数CountStringが実行
// この場合では引数hogeの文字列の長さが表示される
Console.WriteLine(countString("hoge"));
Console.ReadLine();
}
static int CountString(string s) // (2)
{
return s.Length;
}
}
実際にデリゲートが使われるのはコールバック関数の時が多いようで、何かの処理が終わったあとに、呼び出したい処理設定に使われるよう。
あるURLにHTTPリクエストしたあとに、そのHTTPレスポンスに対して、HTTPのスターテスコード返す例を考える。
class Program
{
// HTTPレスポンスを処理する関数を定義するDegate型
// 引数にはHTTPレスポンスを表すHttpResponseMessageを定義
public delegate void Callback(HttpResponseMessage res); // (1)
static void Main(string[] args)
{
// (1)で定義したCallback型のDelegateである変数callbackを定義
// 変数callbackには、(2)で定義した関数GetStatusCodeを代入
Callback callback = GetStatusCode;
// (3)で定義した関数を呼び出す。
//第1引数に指定したURLアクセスに対するHTTPレスポンスを、第2引数に指定したコールバック関数で処理
HttpRequest("http://www.yahoo.co.jp/",callback);
Console.ReadLine();
}
// HTTPレスポンスを処理するためのコールバック関数
// HTTPレスポンスのステータスコードを標準出力
static void GetStatusCode(HttpResponseMessage res) // (2)
{
Console.WriteLine(res.StatusCode.ToString());
}
// 第1引数に指定したURLにアクセスした際のHTTPレスポンスに対して、第2引数で指定したCallback型のDelegateで処理する
async static void HttpRequest(String url,Callback callback) // (3)
{
using (HttpClient httpClient = new HttpClient())
{
// HttpClientクラスを使って、第1引数に指定したURLにアクセスして、そのHTTPレスポンスを取得
HttpResponseMessage res = await httpClient.GetAsync(url);
// 第2引数で指定したCallback型のDelegateで取得したHTTPレスポンスを処理
callback(res);
}
}
}
ラムダ式を使うとこのコードがシンプルにかける。
class Program
{
static void Main(string[] args)
{
HttpRquest("http://www.yahoo.co.jp/", (res) => {
Console.WriteLine(res.StatusCode.ToString());
});
Console.ReadLine();
}
async static void HttpRquest(String url, Action<HttpResponseMessage> callback)
{
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage res = await httpClient.GetAsync(url);
callback(res);
}
}
}
GetStatusCode関数がなくなったので、HttpRequest関数の第2引数にラムダ式を指定している。
HTTPレスポンスを取得するためだけの処理のためにGetStatusCode関数を定義するのも面倒なので、かわりに、その関数の引数と、処理や戻り値を定義するというのが無名関数であるラムダ式なのである。