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,發現有錯誤訊息。
經查詢後,似乎是於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:
SyncPosition.cs:
OperationCode.cs:
ParameterCode.cs:
EventCode.cs:
BaseEvent.cs:
NewPlayerEvent.cs:
Character.cs:
以下提供Server端原始碼:
SyncOtherPlayerHandler.cs:
SyncPositionHandler.cs:
LoginHandler.cs:
NoliahFantasyServer.cs:
最後來測試遊戲看看吧,這次就成功啦!
可是發現了一個很好笑的Bug,移動自己的時候,另一個複製的玩家也會跟著移動。這個Bug會於下一章介紹位置資訊同步的時候一併解決。
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,發現有錯誤訊息。
經查詢後,似乎是於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:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon;
public class PhotonEngine : MonoBehaviour, IPhotonPeerListener {
private Dictionary requestDict = new Dictionary ();
private Dictionary eventDict = new Dictionary ();
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
}
SyncPosition.cs:
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 onlinePlayerList;
void Awake(){
onlinePlayerList = new Dictionary ();
}
void Start () {
syncPosiRequest = GetComponent();
syncOtherPlayerRequest = GetComponent ();
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());
}
}
IEnumerator SyncOtherPlayerPosition(){
yield return new WaitForSeconds (0.2f);
syncOtherPlayerRequest.OnDefaultRequest ();
}
}
OperationCode.cs:
public enum OperationCode : byte
{
Login,
Signup,
SyncPosition,
SyncOtherPlayerPosition
}
ParameterCode.cs:
public enum ParameterCode : byte
{
Acccount,
Password,
Position,
x,
y,
z,
AccountList
}
EventCode.cs:
public enum EventCode : byte
{
NewPlayer
}
BaseEvent.cs:
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:
using System; using ExitGames.Client.Photon; ////// 當Server傳來Event告知有其他Player登入場景,需要在場景添加Player物件的Event /// public class NewPlayerEvent : BaseEvent { SyncPosition syncPosition; void Awake(){ syncPosition = GetComponent(); } 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()); } }
Character.cs:
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.center = colliderCenter;
capsuleCollider.radius = colliderRadius;
capsuleCollider.height = colliderHeight;
myRigidbody = gameObject.AddComponent ();
myRigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
myRigidbody.constraints = RigidbodyConstraints.FreezeRotation;
AudioSource audioSource = gameObject.AddComponent ();
audioSource.volume = volume;
animator = gameObject.AddComponent ();
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.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;
}
}
}
}
以下提供Server端原始碼:
SyncOtherPlayerHandler.cs:
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 userAccountList = new List();
foreach(MyClientPeer otherPeer in NoliahFantasyServer.peerList){
if(OtherPeerHasUserAccount(otherPeer, myClientPeer)){
// 紀錄其他玩家的帳號清單
userAccountList.Add(otherPeer.userAccount);
NoliahFantasyServer.logger.Info("其他線上玩家的帳號:" + otherPeer.userAccount);
}
}
// 設定回傳Data
Dictionary responseData = new Dictionary();
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 eventDataDict = new Dictionary();
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;
}
}
}
SyncPositionHandler.cs:
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:
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:
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 handlerDict =
new Dictionary();
// 透過PeerList,可向任何一個客戶端發送數據
public static List peerList = new List();
// 當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);
}
}
}
最後來測試遊戲看看吧,這次就成功啦!
可是發現了一個很好笑的Bug,移動自己的時候,另一個複製的玩家也會跟著移動。這個Bug會於下一章介紹位置資訊同步的時候一併解決。
























留言
張貼留言