Related Posts Plugin for WordPress, Blogger...

6-26 Sending Event To Instantiate Player GameObject When Player Login

上一章已經介紹如何上傳玩家位置的方法了,接下來,當其他玩家登錄遊戲的時候,也應該要在遊戲場景中實例化新的玩家物件。首先,介紹要實作的流程會讓大家感到比較清楚:

1. 當玩家登錄時,發送Request,要求取得其他玩家的資訊。
2. Server發送Response,將目前線上玩家的所有資訊回傳。
3. 玩家接收到Response後,依照線上玩家的資訊列表,產生玩家的GameObject物件。
4. Server發送Event給其他線上玩家,告知有新的玩家上線。
5. 其他線上玩家接收到Event,於場景中產生一個新的玩家的GameObject。

上述便是此次要實作的流程。

首先,在PhotonServer端的MyClientPeer,新增變數,用來記錄Account與x, y, z位置資訊。當玩家與PhotonServer連線時,便會產生一個ClientPeer物件,所以直接將該名玩家的資訊記錄在ClientPeer物件中。

接著,在SyncPositionHandler.cs中的OnOperationRequest,把來自Client端的位置資訊儲存到ClientPeer的變數中。

同時,也要在LoginHandler中把來自Client端的Account,儲存到ClientPeer的變數。

接著我們回到Unity端來實作SyncOtherPlayerRequest.cs,這個類別的作用是初始化場景時,負責發送Request,要求取得其他玩家的資訊。

所以在OnOperationResponse中,會從Server端取得string陣列,裡面儲存已登入玩家的帳號。這邊,我呼叫了SyncPosition類別的AddNewPlayer方法,待會會告訴大家如何撰寫。
另外提醒,PhotonServer預設支援可自動序列化的類型,或者如何實作自定義類別的序列化,請參考下列網址:
https://doc.photonengine.com/en-us/onpremise/current/reference/serialization-in-photon

現在來到SyncPosition.cs,我新增了一個Dictionary管理登入的線上玩家(不包含自己)。

大家可以看到AddNewPlayer中,先從onlinePlayerList確定這個帳號不在列表中,然後使用Instantiate產生GameObject,並將PlayerMovement類別存入onlinePlayerList中,以後同步位置時需要使用。

接著我們替這次的Request增加一個OperationCode,名為SyncOtherPlayerPosition。

來到場景中的SyncPosition物件,將SyncOtherPlayerRequest.cs加進去,並記得選擇正確的OperationCode。

接下來,回到PhotonServer端,讓我們來處理SyncOtherPlayerHandler.cs。

再次說明,本次的Request需要將線上玩家的清單傳回給Client。所以,在OnOperationRequest方法中,我們需要從PeerList中剔除目前正在登錄的自己,然後將其他線上玩家的清單傳回去。

處理完Request後,Server還必須發送Event給其他線上玩家,告知有新的玩家上線。

於是,我們要再回到Unity端,處理來自Server發送的Event。同Request的做法一樣,我們使用Dictionary統一管理不同功用的Event,所有Event的處理都會繼承我自定義的BaseEvent類別。

在PhotonEngine.cs中提供AddEvent跟RemoveEvent,將事件加入到Dictionary中。

然後在OnEvent方法中,藉由EventCode從Dictionary中取得正確的Event物件,並處理OnEventData方法。

BaseEvent.cs的寫法與Request.cs相似。若不懂我用的這個架構,請參考之前的文章:
6-14 Using Dictionary To Manage All Request
https://3dactionrpg.blogspot.com/2018/05/6-14-using-dictionary-to-manage-all.html


接著新增NewPlayerEvent.cs,繼承BaseEvent,在OnEventData中處理來自Server的訊息。在流程中,要處理其他線上玩家登入遊戲的情況,所以需要於場景中產生一個新的玩家的GameObject

撰寫完成後,記得將NewPlayerEvent.cs放進場景中的SyncPosition。


此時開啟遊戲測試看看,要從登入畫面進入遊戲。首先輸入帳號aaa。

接著使用其他的設備進行登入,使用帳號bbb。

場景中確實產生了第二個玩家,但是不知道為什麼腳都陷進地面底下了。

看了一下Console,發現有錯誤訊息。
Failed to create agent because it is not close enough to the NavMesh

經查詢後,似乎是於Awake階段,透過AddComponent新增NavMeshAgent,會出現這個問題,而且還僅限於Instatiate的時候。我原本放在場景中的Player就一點事情都沒有。為了解決這個問題,只好修改我的Character.cs,將AddComponent<NavMeshAgent>放到Start方法中。
另外,我也有調查網路上針對此問題的其他解決方案,但對我來說都沒有效果,大家參考看看。
https://forum.unity.com/threads/failed-to-create-agent-because-it-is-not-close-enough-to-the-navmesh.125593/

https://forum.unity.com/threads/failed-to-create-agent-because-it-is-not-close-enough-to-the-navmesh.500553/

以下提供本次修改的程式碼:
以下提供Client端原始碼:
PhotonEngine.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon;
 
public class PhotonEngine : MonoBehaviour, IPhotonPeerListener {
 
 private Dictionary<operationcode request=""> requestDict = new Dictionary<operationcode request=""> ();
 private Dictionary<eventcode baseevent=""> eventDict = new Dictionary<eventcode baseevent=""> ();
 
 PhotonPeer peer;
 public static PhotonEngine Instance;
 public static string userAccount;
 
 void Awake() {
  if (Instance == null) {
   Instance = this;
   DontDestroyOnLoad (this);
  } else if (Instance != this) {
   Destroy (gameObject);
   return;
  }
 }
 
 void Start () {
  // 透過Listener回應伺服器的Response
  peer = new PhotonPeer (this, ConnectionProtocol.Udp);
  peer.Connect ("anoneko.cloudapp.net:5055", "NoliahFantasyServer");
 }
  
 void Update () {
  // 必須在Update一直呼叫Service方法才能持續連線到Photon
  peer.Service ();
 }
 
 void OnDestroy(){
  if (peer != null && peer.PeerState == PeerStateValue.Connected) {
   peer.Disconnect ();
  }
 }
 
 public PhotonPeer GetPeer(){
  return peer;
 }
 
 public void AddRequest(Request request){
  requestDict.Add (request.operationCode, request);
 }
 
 public void RemoveRequest(Request request){
  requestDict.Remove (request.operationCode);
 }
 
 public void AddEvent(BaseEvent baseEvent){
  eventDict.Add (baseEvent.eventCode, baseEvent);
 }
 
 public void RemoveEvent(BaseEvent baseEvent){
  eventDict.Remove(baseEvent.eventCode);
 }
 
 #region IPhotonPeerListener implementation
 
 public void DebugReturn (DebugLevel level, string message)
 {
 }
 
 // 接收由Server端傳回的Response
 public void OnOperationResponse (OperationResponse operationResponse)
 {
  OperationCode opCode = (OperationCode)operationResponse.OperationCode;
  Request request = null;
  requestDict.TryGetValue (opCode, out request);
  request.OnOperationResponse (operationResponse);
 }
 
 public void OnStatusChanged (StatusCode statusCode)
 {
 }
 
 // 接收由Server端發送的Event事件
 public void OnEvent (EventData eventData)
 {
  EventCode eventCode = (EventCode)eventData.Code;
  BaseEvent baseEvent = null;
  eventDict.TryGetValue (eventCode, out baseEvent);
  baseEvent.OnEventData (eventData);
 }
 
 #endregion
}
 
</eventcode></eventcode></operationcode></operationcode>

SyncPosition.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Character;
using RPG.Core;
 
public class SyncPosition : MonoBehaviour {
 
 [SerializeField] GameObject playerPrefab;
 
 GameObject player;
 SyncPositionRequest syncPosiRequest;
 SyncOtherPlayerRequest syncOtherPlayerRequest;
 Vector3 lastPosition;
 Dictionary<string playermovement=""> onlinePlayerList;
 
 void Awake(){
  onlinePlayerList = new Dictionary<string playermovement=""> ();
 }
 
 void Start () {
  syncPosiRequest = GetComponent<syncpositionrequest>();
  syncOtherPlayerRequest = GetComponent<syncotherplayerrequest> ();
  player = Game.Instance.playerMovement.gameObject;
  // 初始化Player位置
  lastPosition = player.transform.position;
  // 每段時間同步玩家位置
  StartCoroutine (UploadPosition ());
  // 同步其他玩家位置
  StartCoroutine (SyncOtherPlayerPosition ());
 }
 
 IEnumerator UploadPosition(){
  while (true) {
   // 每秒同步五次
   yield return new WaitForSeconds (0.2f);
   // 判斷是否有移動,若Player沒有在移動便不需要同步
   Vector3 nowPosition = player.transform.position;
   if(Vector3.Distance(nowPosition, lastPosition) > 0.1f){
    lastPosition = nowPosition;
    syncPosiRequest.position = nowPosition;
    // 發送位置更新訊息
    syncPosiRequest.OnDefaultRequest ();
   }
  }
 }
 
 public void AddNewPlayer(string account){
  if (onlinePlayerList.ContainsKey (account) == false) {
   // TODO 製作Player Factory改善這個問題
   // Position使用此遊戲物件的位置
   GameObject player = Instantiate (playerPrefab, transform.position, Quaternion.identity);
   onlinePlayerList.Add (account, player.GetComponent<playermovement>());
  }
 }
 
 IEnumerator SyncOtherPlayerPosition(){
  yield return new WaitForSeconds (0.2f);
  syncOtherPlayerRequest.OnDefaultRequest ();
 }
 
 
}
 
</playermovement></syncotherplayerrequest></syncpositionrequest></string></string>

OperationCode.cs:

1
2
3
4
5
6
7
public enum OperationCode : byte
{
 Login,
 Signup,
 SyncPosition,
 SyncOtherPlayerPosition
}

ParameterCode.cs:

1
2
3
4
5
6
7
8
9
10
public enum ParameterCode : byte
{
 Acccount,
 Password,
 Position,
 x,
 y,
 z,
 AccountList
}

EventCode.cs:

1
2
3
4
public enum EventCode : byte
{
 NewPlayer
}

BaseEvent.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using UnityEngine;
using ExitGames.Client.Photon;
 
public abstract class BaseEvent : MonoBehaviour
{
 public EventCode eventCode;
 
 public abstract void OnEventData(EventData eventData);
 
 public virtual void Start(){
  PhotonEngine.Instance.AddEvent (this);
 }
 
 public void Destory(){
  PhotonEngine.Instance.RemoveEvent (this);
 }
}

NewPlayerEvent.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using ExitGames.Client.Photon;
 
/// <summary>
/// 當Server傳來Event告知有其他Player登入場景,需要在場景添加Player物件的Event
/// </summary>
public class NewPlayerEvent : BaseEvent
{
 SyncPosition syncPosition;
 
 void Awake(){
  syncPosition = GetComponent<syncposition> ();
 }
 
 public override void OnEventData (EventData eventData)
 {
  print ("處理來自Server的NewPlayerEvent");
  object account;
  eventData.Parameters.TryGetValue ((byte)ParameterCode.Acccount, out account);
  // 通知SyncPosition產生其他的Player物件
  syncPosition.AddNewPlayer(account.ToString());
 }
}
 
 
</syncposition>

Character.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
using System;
using UnityEngine;
using UnityEngine.AI;
using RPG.CameraUI;
 
namespace RPG.Character{
 [SelectionBase]
 public class Character : MonoBehaviour
 {
  [Header("Audio")]
  [Range(0.0f, 1.0f)][SerializeField] float volume = 1f;
 
  [Header("Capsule Collider")]
  [SerializeField] Vector3 colliderCenter = new Vector3(0, 1, 0);
  [SerializeField] float colliderRadius = 0.3f;
  [SerializeField] float colliderHeight = 1.5f;
 
  [Header("Animator")]
  [SerializeField] RuntimeAnimatorController animatorController;
  [SerializeField] AnimatorOverrideController animatorOverrideController;
  [SerializeField] Avatar avatar;
  [Range(0.0f, 1.0f)][SerializeField] float animatorForwardCap = 1f;
 
  [Header("Movement")]
  [SerializeField] float moveSpeedMultiplier = 1f;
  [SerializeField] float animationSpeedMultiplier = 1f;
  [SerializeField] float movingTurnSpeed = 1800;
  [SerializeField] float stationaryTurnSpeed = 1800;
  [SerializeField] float moveThreshold = 1f;
 
  [Header("Nav Mesh Agent")]
  [SerializeField] float steeringSpeed = 1.0f;
 
  NavMeshAgent navMeshAgent;
  Animator animator;
  Rigidbody myRigidbody;
  CapsuleCollider capsuleCollider;
  float turnAmount;
  float forwardAmount;
  bool isAlive = true;
 
  void Awake(){
   AddRequiredComponents ();
  }
 
  private void AddRequiredComponents(){
   capsuleCollider = gameObject.AddComponent<capsulecollider> ();
   capsuleCollider.center = colliderCenter;
   capsuleCollider.radius = colliderRadius;
   capsuleCollider.height = colliderHeight;
 
   myRigidbody = gameObject.AddComponent<rigidbody> ();
   myRigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
   myRigidbody.constraints = RigidbodyConstraints.FreezeRotation;
 
   AudioSource audioSource = gameObject.AddComponent<audiosource> ();
   audioSource.volume = volume;
 
   animator = gameObject.AddComponent<animator> ();
   animator.runtimeAnimatorController = animatorController;
   animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
   animator.avatar = avatar;
  }
 
  void Start(){
   // 當Instatiate時,NavMeshAgent在Awake階段進行AddComponent
   // 會產生下列Bug,故移動到Start內
   // Failed to create agent because it is not close enough to the NavMesh
   navMeshAgent = gameObject.AddComponent<navmeshagent> ();
   navMeshAgent.updateRotation = false;
   navMeshAgent.updatePosition = true;
   navMeshAgent.speed = steeringSpeed;
  }
 
  void Update(){
   if (isAlive && navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance) {
    Move(navMeshAgent.desiredVelocity);
   } else {
    Move (Vector3.zero);
   }
  }
 
  void Move(Vector3 movement)
  {
   SetForwardAndTurn (movement);
   ApplyExtraTurnRotation();
   UpdateAnimator();
  }
 
  public float GetAnimSpeedMultiplier(){
   return animator.speed;
  }
 
  public void Kill(){
   isAlive = false;
  }
 
  public void SetDestination(Vector3 worldPos){
   if (isAlive) {
    navMeshAgent.SetDestination (worldPos);
   }
  }
 
  public void SetStopDistance(float stoppingDistance){
   if (isAlive) {
    navMeshAgent.stoppingDistance = stoppingDistance;
   }
  }
 
  public AnimatorOverrideController GetOverrideController(){
   return animatorOverrideController;
  }
 
  private void SetForwardAndTurn(Vector3 movement){
   // 將Wolrd相對的向量轉換成Local相對的向量
   if (movement.magnitude > moveThreshold) {
    movement.Normalize ();
   }
   // 將Worldspace的方向轉換成LocalSpace
   Vector3 localMovement = transform.InverseTransformDirection(movement);
   turnAmount = Mathf.Atan2(localMovement.x, localMovement.z);
   forwardAmount = localMovement.z;
  }
 
  void UpdateAnimator()
  {
   // update the animator parameters
   animator.SetFloat("Forward", forwardAmount * animatorForwardCap, 0.1f, Time.deltaTime);
   animator.SetFloat("Turn", turnAmount, 0.1f, Time.deltaTime);
   animator.speed = animationSpeedMultiplier;
  }
 
  void ApplyExtraTurnRotation()
  {
   // help the character turn faster (this is in addition to root rotation in the animation)
   float turnSpeed = Mathf.Lerp(stationaryTurnSpeed, movingTurnSpeed, forwardAmount);
   transform.Rotate(0, turnAmount * turnSpeed * Time.deltaTime, 0);
  }
 
  void OnAnimatorMove(){
   if (Time.deltaTime > 0) {
    Vector3 velocity = (animator.deltaPosition * moveSpeedMultiplier) / Time.deltaTime;
    // 保持y軸的速度不變
    velocity.y = myRigidbody.velocity.y;
    myRigidbody.velocity = velocity;
   }
  }
 }
}
</navmeshagent></animator></audiosource></rigidbody></capsulecollider>

以下提供Server端原始碼:
SyncOtherPlayerHandler.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System.Collections.Generic;
using Photon.SocketServer;
using NoliahFantasyServer.Constant;
 
namespace NoliahFantasyServer.Handler
{
 public class SyncOtherPlayerHandler : BaseHandler
    {
        public SyncOtherPlayerHandler()
        {
            operationCode = OperationCode.SyncOtherPlayerPosition;
        }
 
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, MyClientPeer myClientPeer)
        {
            List<string> userAccountList = new List<string>();
            foreach(MyClientPeer otherPeer in NoliahFantasyServer.peerList){
                if(OtherPeerHasUserAccount(otherPeer, myClientPeer)){
                    // 紀錄其他玩家的帳號清單
                    userAccountList.Add(otherPeer.userAccount);
                    NoliahFantasyServer.logger.Info("其他線上玩家的帳號:" + otherPeer.userAccount);
                }
            }
 
            // 設定回傳Data
            Dictionary<byte object=""> responseData = new Dictionary<byte object="">();
            responseData.Add((byte)ParameterCode.AccountList, userAccountList.ToArray());
            OperationResponse operationResponse = new OperationResponse(
                (byte)OperationCode.SyncOtherPlayerPosition, responseData);
            // 發送Response
            myClientPeer.SendOperationResponse(operationResponse, sendParameters);
 
            // 通知其他Peer有玩家登入場景
            foreach (MyClientPeer otherPeer in NoliahFantasyServer.peerList)
            {
                NoliahFantasyServer.logger.Info("發送Event給其他線上玩家的帳號:" + otherPeer.userAccount);
                if (OtherPeerHasUserAccount(otherPeer, myClientPeer))
                {
                    Dictionary<byte object=""> eventDataDict = new Dictionary<byte object="">();
                    eventDataDict.Add((byte)ParameterCode.Acccount, myClientPeer.userAccount);
 
                    EventData eventData = new EventData((byte)EventCode.NewPlayer);
                    eventData.Parameters = eventDataDict;
                    otherPeer.SendEvent(eventData, sendParameters);
                }
            }
        }
 
        bool OtherPeerHasUserAccount(MyClientPeer otherPeer, MyClientPeer me){
            return string.IsNullOrEmpty(otherPeer.userAccount) == false && otherPeer != me;
        }
    }
}
 
</byte></byte></byte></byte></string></string>

SyncPositionHandler.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Collections.Generic;
using Photon.SocketServer;
using NoliahFantasyServer.Constant;
 
namespace NoliahFantasyServer.Handler
{
    public class SyncPositionHandler : BaseHandler
    {
        public SyncPositionHandler()
        {
            operationCode = OperationCode.SyncPosition;
        }
 
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, MyClientPeer myClientPeer)
        {
            object xPosition;
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.x, out xPosition);
            object yPosition;
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.y, out yPosition);
            object zPosition;
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.z, out zPosition);
 
            myClientPeer.xPosition = (float)xPosition;
            myClientPeer.yPosition = (float)yPosition;
            myClientPeer.zPosition = (float)zPosition;
 
        }
    }
}

LoginHandler.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using Photon.SocketServer;
using NoliahFantasyServer.Constant;
using NoliahFantasyServer.Manager;
 
namespace NoliahFantasyServer.Handler
{
    public class LoginHandler : BaseHandler
    {
        public LoginHandler(){
            operationCode = OperationCode.Login;
        }
 
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, MyClientPeer myClientPeer)
        {
            // 從OperationRequest取得帳號密碼資訊
            object account;
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.Acccount, out account);
            object password;
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.Password, out password);
 
            // 使用UserManager向資料庫驗證使用者的帳號密碼
            UserManager userManager = new UserManager();
            bool isVerify = userManager.VerifyUser(account as string, password as string);
 
            // 使用short類型的ReturnCode簡單回傳結果
            OperationResponse operationResponse = new OperationResponse((byte)OperationCode.Login);
            if(isVerify){
                operationResponse.ReturnCode = (short)ReturnCode.Success;
            }else{
                operationResponse.ReturnCode = (short)ReturnCode.Failed;
            }
            // 發送Response
            myClientPeer.SendOperationResponse(operationResponse, sendParameters);
 
            // 紀錄User Account
            NoliahFantasyServer.logger.Info("玩家登入:" + account.ToString());
            myClientPeer.userAccount = account.ToString();
        }
    }
}

NoliahFantasyServer.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System.IO;
using System.Collections.Generic;
using Photon.SocketServer;
using ExitGames.Logging;
using log4net.Config;
using NoliahFantasyServer.Constant;
using NoliahFantasyServer.Handler;
 
namespace NoliahFantasyServer
{
    public class NoliahFantasyServer : ApplicationBase
    {
 
        public static readonly ILogger logger = LogManager.GetCurrentClassLogger();
        public static Dictionary<operationcode basehandler=""> handlerDict =
            new Dictionary<operationcode basehandler="">();
        // 透過PeerList,可向任何一個客戶端發送數據
        public static List<myclientpeer> peerList = new List<myclientpeer>();
 
        // 當Client端發出Request的時候
        protected override PeerBase CreatePeer(InitRequest initRequest)
        {
            MyClientPeer peer = new MyClientPeer(initRequest);
            peerList.Add(peer);
            return peer;
        }
 
        // Server端啟動的時候初始化
        protected override void Setup()
        {
            InitLogger();
            InitHandler();
        }
 
        // Server端關閉的時候
        protected override void TearDown()
        {
        }
 
        void InitLogger()
        {
            // 日誌初始化
            log4net.GlobalContext.Properties["Photon:ApplicationLogPath"] =
                Path.Combine(this.ApplicationRootPath, "bin_Win64", "log");
            FileInfo loggerConfig = new FileInfo(Path.Combine(this.BinaryPath, "log4net.config"));
            if (loggerConfig.Exists)
            {
                // 設置使用log4net的Log功能
                LogManager.SetLoggerFactory(ExitGames.Logging.Log4Net.Log4NetLoggerFactory.Instance);
                // 讓log4net讀取config
                XmlConfigurator.ConfigureAndWatch(loggerConfig);
            }
            logger.Info("Setup Log4Net Compeleted!");
        }
 
        void InitHandler(){
            LoginHandler loginHandler = new LoginHandler();
            handlerDict.Add(loginHandler.operationCode, loginHandler);
            SignupHandler signupHandler = new SignupHandler();
            handlerDict.Add(signupHandler.operationCode, signupHandler);
            SyncPositionHandler syncPositionHandler = new SyncPositionHandler();
            handlerDict.Add(syncPositionHandler.operationCode, syncPositionHandler);
            SyncOtherPlayerHandler syncOtherPlayerHandler = new SyncOtherPlayerHandler();
            handlerDict.Add(syncOtherPlayerHandler.operationCode, syncOtherPlayerHandler);
        }
 
    }
}
 
</myclientpeer></myclientpeer></operationcode></operationcode>

最後來測試遊戲看看吧,這次就成功啦!

可是發現了一個很好笑的Bug,移動自己的時候,另一個複製的玩家也會跟著移動。這個Bug會於下一章介紹位置資訊同步的時候一併解決。

留言