Catalog
World Chat System and Resource Trading System
Development of World Chat System Interface
Development of World Chat System 1
World Chat System Development 2
World Chat System Development 3
World Chat System Development 4
World Chat System Development 5
World Chat System Development 6
World Chat System Development 7
Demonstration debugging and bug solving process
Making General Purchase Window
Development of Resource Trading System 1
Resource trading system development 2
Resource trading system development 3
Resource Trading System Development 4
Resource trading system development 5
Message Frequency Timing Control
Client Integration of PETimer Timing Plug-in
Server Integrates PETimer Timing Plug-in
Timing detection using independent threads
Development of Physical Recovery System 1
Development of Physical Recovery System 2
Development of Physical Recovery System 3
Development of Physical Recovery System 4
Development of Physical Recovery System 5
Development of Physical Recovery System 6
World Chat System and Resource Trading System
Development of World Chat System Interface
Development of World Chat System 1
Create a ChatWnd script
using UnityEngine; public class ChatWnd : WindowRoot { protected override void InitWnd() { base.InitWnd(); } public void ClickCloseBtn() { audioSvc.PlayUIAudio(Constants.UIClickBtn); SetWndState(false); } }
Add a method to open the chat system in MainCityWnd.
public void ClickChatBtn() { audioSvc.PlayUIAudio(Constants.UIOpenPage); MainCitySys.Instance.OpenChatWnd(); }
This method is implemented by calling the method in MainCitySys
public void OpenChatWnd() { chatWnd.SetWndState(); }
World Chat System Development 2
Add a display method to control the chat categorization button
public Text txtChat; public Image imgWorld; public Image imgGuild; public Image imgFriend; private int chatType; private List<string> chatList = new List<string>(); protected override void InitWnd() { base.InitWnd(); chatType = 0; RefreshUI(); } private void RefreshUI() { if(chatType == 0)//world { string chatMsg = ""; for(int i=0;i<chatList.Count;i++) { chatMsg += chatList[i] + "\n"; } SetText(txtChat, chatMsg); SetSprite(imgWorld, PathDefine.BtnType1); SetSprite(imgGuild, PathDefine.BtnType2); SetSprite(imgFriend, PathDefine.BtnType2); } else if(chatType == 1)//Labour Union { SetText(txtChat, "There is no trade union at present."); SetSprite(imgWorld, PathDefine.BtnType2); SetSprite(imgGuild, PathDefine.BtnType1); SetSprite(imgFriend, PathDefine.BtnType2); } else if(chatType == 2)//Good friend { SetText(txtChat, "No Friends Information"); SetSprite(imgWorld, PathDefine.BtnType2); SetSprite(imgGuild, PathDefine.BtnType2); SetSprite(imgFriend, PathDefine.BtnType1); } } public void ClickCloseBtn() { audioSvc.PlayUIAudio(Constants.UIClickBtn); SetWndState(false); }
World Chat System Development 3
Complete the button click events, respectively, are the Send button, the World Chat button, the Guild Chat button and the Friends Chat button.
public InputField iptChat; public void ClickSendBtn() { if(iptChat.text!=null && iptChat.text!= "" && iptChat.text!= " ") { if(iptChat.text.Length > 12) { GameRoot.AddTips("Input information should not exceed 12 words"); } else { //Send network messages to servers } } else { GameRoot.AddTips("No chat information has been entered yet"); } } public void ClickWorldBtn() { audioSvc.PlayUIAudio(Constants.UIClickBtn); chatType = 0; RefreshUI(); } public void ClickGuildBtn() { audioSvc.PlayUIAudio(Constants.UIClickBtn); chatType = 1; RefreshUI(); } public void ClickFriendBtn() { audioSvc.PlayUIAudio(Constants.UIClickBtn); chatType = 2; RefreshUI(); }
World Chat System Development 4
Firstly, two communication message classes are defined in GameMsg on the server side. One is SndChat to send messages to the server, the other is PshChat to broadcast messages.
[Serializable] public class SndChat { public string chat; } [Serializable] public class PshChat { public string name; public string chat; }
CMD codes are defined as follows:
SndChat = 205, PshChat = 206,
Sending messages in the client's hatWnd class
//Send network messages to servers GameMsg msg = new GameMsg { cmd = (int)CMD.SndChat, sndChat = new SndChat { chat = iptChat.text } }; iptChat.text = ""; netSvc.SendMsg(msg);
World Chat System Development 5
Processing and broadcasting messages sent on the server side, which are processed in ChatSys
using PEProtocol; using System.Collections.Generic; public class ChatSys { private static ChatSys instance = null; public static ChatSys Instance { get { if (instance == null) { instance = new ChatSys(); } return instance; } } private CacheSvc cacheSvc = null; public void Init() { cacheSvc = CacheSvc.Instance; PECommon.Log("ChatSys Init Done."); } public void SndChat(MsgPack pack) { //Get the role name SndChat data = pack.msg.sndChat; PlayerData pd = cacheSvc.GetPlayerDataBySession(pack.session); GameMsg msg = new GameMsg { cmd = (int)CMD.PshChat, pshChat = new PshChat { name = pd.name, chat = data.chat } }; //Broadcast all online clients List<ServerSession> lst = cacheSvc.GetOnlineServerSessions(); for(int i = 0;i<lst.Count;i++) { lst[i].SendMsg(msg); } } }
Where GetOnline Server Sessions is a session to get all client online users
World Chat System Development 6
Method of broadcasting all clients before optimization
//Broadcast all online clients List<ServerSession> lst = cacheSvc.GetOnlineServerSessions(); byte[] bytes = PENet.PETool.PackNetMsg(msg);//Serialize messages into binary for(int i = 0;i<lst.Count;i++) { //lst[i].SendMsg(msg); can be optimized, preferably using serialized messages for sending lst[i].SendMsg(bytes); }
World Chat System Development 7
This section is responsible for receiving server-side messages and displaying them on the client side.
public void PshChat(GameMsg msg) { chatWnd.AddChatMsg(msg.pshChat.name, msg.pshChat.chat); }
public void AddChatMsg(string name,string chat) { chatList.Add(Constants.Color(name + ": ", TxtColor.Blue) + chat); if(chatList.Count>12) { chatList.RemoveAt(0); } RefreshUI(); }
Demonstration debugging and bug solving process
There is a bug. When one client sends a message, another client clicks the chat button to report an error. This is because before the chat box comes out, the method on the chat box is called. At this point, we just need to activate the chat box and then call the method.
public void AddChatMsg(string name,string chat) { chatList.Add(Constants.Color(name + ": ", TxtColor.Blue) + chat); if(chatList.Count>12) { chatList.RemoveAt(0); } if(GetWndState())//In the active state, refresh again. RefreshUI(); }
Making General Purchase Window
Development of Resource Trading System 1
Create scripts for BuyWnd, then initialize and register button events.
using UnityEngine; public class BuyWnd : WindowRoot { protected override void InitWnd() { base.InitWnd(); } }
public void ClickMKCoinBtn() { audioSvc.PlayUIAudio(Constants.UIOpenPage); MainCitySys.Instance.OpenBuyWnd(); } public void ClickBuyPowerBtn() { audioSvc.PlayUIAudio(Constants.UIOpenPage); MainCitySys.Instance.OpenBuyWnd(); }
public void OpenBuyWnd() { buyWnd.SetWndState(); }
Resource trading system development 2
Perfect the previous ChatWnd, and then open the ChatWnd to give its type, whether to buy physical strength or gold coins.
public Text txtInfo; private int buyType;//0:Physical strength 1:Gold coin public void SetBuyInfoType(int type) { this.buyType = type; } protected override void InitWnd() { base.InitWnd(); RefreshUI(); } private void RefreshUI() { switch(buyType) { case 0: //physical strength txtInfo.text = "Whether to spend or not" + Constants.Color("10 masonry", TxtColor.Red) + "purchase" + Constants.Color("100 physical strength", TxtColor.Green) + "?"; break; case 1: //Gold coin txtInfo.text = "Whether to spend or not" + Constants.Color("10 masonry", TxtColor.Red) + "purchase" + Constants.Color("1000 Gold coin", TxtColor.Green) + "?"; break; } } public void ClickSureBtn() { //Send Network Purchase Information TODO } public void ClickCloseBtn() { audioSvc.PlayUIAudio(Constants.UIClickBtn); SetWndState(false); }
public void OpenBuyWnd(int type) { buyWnd.SetBuyInfoType(type); buyWnd.SetWndState(); }
Registration Event Method with Two Buttons in MainCityWnd
public void ClickMKCoinBtn() { audioSvc.PlayUIAudio(Constants.UIOpenPage); MainCitySys.Instance.OpenBuyWnd(1); } public void ClickBuyPowerBtn() { audioSvc.PlayUIAudio(Constants.UIOpenPage); MainCitySys.Instance.OpenBuyWnd(0); }
Resource trading system development 3
Add Client Click to Determine Button Event and Send Information to Client
public void ClickSureBtn() { //Sending Network Purchase Information GameMsg msg = new GameMsg { cmd = (int)CMD.ReqBuy, reqBuy = new ReqBuy { type = buyType, cost =10, } }; netSvc.SendMsg(msg); }
This section is mainly responsible for the content of the BuyWnd client. The values that need to be sent and returned are as follows
[Serializable] public class ReqBuy { public int type; public int cost; } [Serializable] public class RspBuy { public int type; public int diamond; public int coin; public int power; }
The BuySys script is as follows:
using PEProtocol; public class BuySys { private static BuySys instance = null; public static BuySys Instance { get { if (instance == null) { instance = new BuySys(); } return instance; } } private CacheSvc cacheSvc = null; public void Init() { cacheSvc = CacheSvc.Instance; PECommon.Log("BuySys Init Done."); } public void ReqBuy(MsgPack pack) { ReqBuy data = pack.msg.reqBuy; GameMsg msg = new GameMsg { cmd = (int)CMD.RspBuy, }; //TODO } }
Resource Trading System Development 4
Perfect ReqBuy method and add TODO content
//Getting Cache Information PlayerData pd = cacheSvc.GetPlayerDataBySession(pack.session); if(pd.diamond<data.cost) { msg.err = (int)ErrorCode.LackDiamond; } else { pd.diamond = pd.diamond - data.cost; switch(data.type) {//0 physical strength 1 gold coin case 0: pd.power += 100; break; case 1: pd.coin += 1000; break; } } if(!cacheSvc.UpdatePlayerData(pd.id,pd)) { msg.err = (int)ErrorCode.UpdateDBError; } else { RspBuy rspBuy = new RspBuy { type = data.type, diamond = pd.diamond, coin = pd.coin, power = pd.power }; msg.rspBuy = rspBuy; } pack.session.SendMsg(msg);
Resource trading system development 5
Add the client processing method RspBuy()
public void RspBuy(GameMsg msg) { RspBuy data = msg.rspBuy; GameRoot.Instance.SetPlayerDataByBuy(data); GameRoot.AddTips("Successful Purchase"); maincityWnd.RefreshUI(); buyWnd.SetWndState(false); } public void SetPlayerDataByBuy(RspBuy data) { playerData.diamond = data.diamond; playerData.coin = data.coin; playerData.power = data.power; }
In order to prevent network latency from causing players to click on the confirmation multiple times leading to repeated purchases, add a non-interactive after clicking on the confirmation.
Every time a page pops up, the interaction is set to true
Message Frequency Timing Control
The first is a simple protocol, added in ChatWnd
private bool canSend = true; public void ClickSendBtn() { if (!canSend) { GameRoot.AddTips("Chat messages are sent every five seconds"); return; } if (iptChat.text!=null && iptChat.text!= "" && iptChat.text!= " ") { if(iptChat.text.Length > 12) { GameRoot.AddTips("Input information should not exceed 12 words"); } else { //Send network messages to servers GameMsg msg = new GameMsg { cmd = (int)CMD.SndChat, sndChat = new SndChat { chat = iptChat.text } }; iptChat.text = ""; netSvc.SendMsg(msg); canSend = false; //Open the coordinator and set canSend to true after 5 seconds StartCoroutine(MsgTimer()); } } else { GameRoot.AddTips("No chat information has been entered yet"); } } IEnumerator MsgTimer() { yield return new WaitForSeconds(5.0f); canSend = true; }
Client Integration of PETimer Timing Plug-in
Here we will use the PETimer timing component that we have learned before. It has the following functions:
1. Dual-end versatility: an efficient and convenient timer based on C # language can run in the server (. net core/.net framework) and Unity client environment.
2. Rich functions: PETimer supports frame number timing and time timing. Timing tasks are cyclic, replaceable and cancelable. You can use separate thread timing (self-setting detection intervals) or external driver timing, such as using the Update() function in MonoBehaviour.
3. Simple integration: Only one PETimer.cs file, just need to instantiate a PETimer class, docking the corresponding API, can be integrated into their game framework, to achieve convenient and efficient timing callback service.
Drag PETimer.cs into the Service folder and create a TimerService class
using System; using UnityEngine; public class TimerSvc : SystemRoot { public static TimerSvc Instance = null; private PETimer pt; public void InitSvc() { Instance = this; pt = new PETimer(); //Setting Log Output pt.SetLog((string info) => { PECommon.Log(info); }); PECommon.Log("Init TimerSvc..."); } public void Update() { pt.Update(); } // Event Time Unit Counting public int AddTimeTask(Action<int> callback,double delay,PETimeUnit timeUnit=PETimeUnit.Millisecond,int count = 1) { return pt.AddTimeTask(callback, delay, timeUnit, count); } }
Testing in GameRoot
//PETimer test TimerSvc.Instance.AddTimeTask((int tid) => { PECommon.Log("Test"); }, 1000);
Server Integrates PETimer Timing Plug-in
To create a TimerSvc script on the server side to control timing, you also need to drag the PETimer.cs file in.
using System; public class TimerSvc { private static TimerSvc instance = null; public static TimerSvc Instance { get { if (instance == null) { instance = new TimerSvc(); } return instance; } } PETimer pt = null; public void Init() { pt = new PETimer(); //Setting Log Output pt.SetLog((string info) => { PECommon.Log(info); }); PECommon.Log("TimerSvc Init Done."); } public void Update() { pt.Update(); } public int AddTimeTask(Action<int> callback, double delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) { return pt.AddTimeTask(callback, delay, timeUnit, count); } }
Timing detection using independent threads
This section is used to improve the previous TimerSvc script. If Update is used all the time, thread detection will always be done, which may result in waste of resources. Here we use an independent detection mechanism.
pt = new PETimer(100);
The timer runs independently and detects every 100 milliseconds. Modify the Writing of TimeSvc
using System; using System.Collections.Generic; public class TimerSvc { class TaskPack { public int tid; public Action<int> cb; public TaskPack(int tid, Action<int> cb) { this.tid = tid; this.cb = cb; } } private static TimerSvc instance = null; public static TimerSvc Instance { get { if (instance == null) { instance = new TimerSvc(); } return instance; } } PETimer pt = null; Queue<TaskPack> tpQue = new Queue<TaskPack>(); private static readonly string tpQueLock = "tpQueLock"; public void Init() { pt = new PETimer(100); //Setting Log Output pt.SetLog((string info) => { PECommon.Log(info); }); pt.SetHandle((Action<int> cb, int tid) => { if (cb != null) { lock (tpQueLock) { tpQue.Enqueue(new TaskPack(tid, cb)); } } }); PECommon.Log("Init TimerSvc..."); } public void Update() { while (tpQue.Count > 0) { TaskPack tp = null; lock (tpQueLock) { tp = tpQue.Dequeue(); } if (tp != null) { tp.cb(tp.tid); } } } public int AddTimeTask(Action<int> callback, double delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) { return pt.AddTimeTask(callback, delay, timeUnit, count); } }
Do a test
TimerSvc.Instance.AddTimeTask((int tid) => { PECommon.Log("xxx"); },1000,PETimeUnit.Millisecond,0);
Development of Physical Recovery System 1
Create a PowerSys to handle physical recovery
//Physical recovery system public class PowerSys { private static PowerSys instance = null; public static PowerSys Instance { get { if (instance == null) { instance = new PowerSys(); } return instance; } } private CacheSvc cacheSvc = null; public void Init() { cacheSvc = CacheSvc.Instance; TimerSvc.Instance.AddTimeTask(CalPowerAdd, PECommon.PowerAddSpace, PETimeUnit.Second, 0); PECommon.Log("PowerSys Init Done."); } private void CalPowerAdd(int tid) { //Computing Physical Strengthen TODO PECommon.Log("Add...Power."); } }
public const int PowerAddSpace = 5;//Minute public const int PowerAddCount = 2;//Physical strength
Development of Physical Recovery System 2
Continue to improve the code of the physical recovery system, which is used to push real-time physical recovery data to all online inquiries.
public void Init() { cacheSvc = CacheSvc.Instance; TimerSvc.Instance.AddTimeTask(CalPowerAdd, PECommon.PowerAddSpace, PETimeUnit.Second, 0); PECommon.Log("PowerSys Init Done."); } private void CalPowerAdd(int tid) { //Computing Physical Strengthen TODO PECommon.Log("All Online Player Calc Power Incress..."); GameMsg msg = new GameMsg { cmd = (int)CMD.PshPower }; msg.pshPower = new PshPower(); //All online players get real-time physical growth push data Dictionary<ServerSession, PlayerData> onlineDic = cacheSvc.GetOnlineCache(); foreach(var item in onlineDic) { PlayerData pd = item.Value; ServerSession session = item.Key; int powerMax = PECommon.GetPowerLimit(pd.lv); if (pd.power >= powerMax) continue; else { pd.power += PECommon.PowerAddCount; if (pd.power >= powerMax) pd.power = powerMax; } if(!cacheSvc.UpdatePlayerData(pd.id,pd)) { msg.err = (int)ErrorCode.UpdateDBError; } else { msg.pshPower.power = pd.power; session.SendMsg(msg); } } }
Development of Physical Recovery System 3
Adding methods to process information and updating the display of physical strength on the client side
public void PshPower(GameMsg msg) { PshPower data = msg.pshPower; GameRoot.Instance.SetPlayerDataByPower(data); maincityWnd.RefreshUI(); }
Development of Physical Recovery System 4
Add a time to the database user information to calculate the time, so as to facilitate the recovery of physical strength for offline users.
Add a method to calculate the current number of milliseconds
//Get the current time (milliseconds from 1970 to the present time of the computer) public long GetNowTime() { return (long)pt.GetMillisecondsTime(); }
When creating an account, add this time information to the user.
Development of Physical Recovery System 5
Processing offline physical recovery requires only processing at login time. Here's the code to add to LoginSys
//Calculating offline physical growth int power = pd.power; long now = timerSvc.GetNowTime(); long milliseconds = now - pd.time; int addPower = (int)(milliseconds / (1000 * 60 * PECommon.PowerAddSpace)) * PECommon.PowerAddCount; if(addPower > 0) { int powerMax = PECommon.GetPowerLimit(pd.lv); if(pd.power < powerMax) { pd.power += addPower; if (pd.power > powerMax) pd.power = powerMax; } } if(power!=pd.power) { cacheSvc.UpdatePlayerData(pd.id, pd); }
Development of Physical Recovery System 6
The method of rewriting server-side offline, calculating the time of each offline, is used to calculate offline physical recovery.
public void ClearOfflineData(ServerSession session) { //Write down time to handle physical strength PlayerData pd = cacheSvc.GetPlayerDataBySession(session); if(pd != null) { pd.time = timerSvc.GetNowTime(); if(!cacheSvc.UpdatePlayerData(pd.id,pd)) { PECommon.Log("Update offline time error", LogType.Error); } cacheSvc.AcctOffLine(session); } }
Prevent accidents or update time when power is updated