unity網路實戰開發(叢林戰爭)-正式開發階段(016-資料庫設計以及登入處理)
使用工具:VS2017,unity3d
使用語言:c#
作者:Gemini_xujian
參考:siki老師-《叢林戰爭》視訊教程
上一篇文章中,已經完成了遊戲場景與開始介面UI的搭建,接下來將對資料庫和登入請求響應等操作進行處理。
01-設計資料庫表(使用者表和戰績表)
首先,我們需要設計資料庫中的表,第一個就是使用者表,用來儲存使用者資訊,包含的列有id/name/password,第二個表是資料表,用來儲存玩家的戰績,包含的列有id/userid/totalCount/winCount,在建立資料庫表的時候,需要我們提前安裝好MySQL,對於資料庫的一些操作,我在前面的文章中有提到過,感興趣的同學可以翻一翻前面的內容。建立好兩個表之後,需要將戰績表的外來鍵設定為使用者表中的主鍵。資料庫結構及表內容如圖所示:
02-處理登入按鈕的點選,校驗賬號資訊是否為空並提示
我們需要在本地對輸入的使用者名稱和密碼進行簡單的校驗,最基本的校驗就是空的情況處理,在loginpanel指令碼中,我們首先得到使用者名稱輸入框和密碼輸入框以及登入和註冊按鈕,然後賦值,賦值的時候需要注意要在start方法和enter方法中都進行賦值,然後為按鈕註冊事件,在LoginPanel中分別建立登陸點選處理方法和註冊點選處理方法,在onloginclick方法中對輸入內容進行判斷,判斷其是否為空。修改後程式碼如下:
LoginPanel.cs:
using DG.Tweening; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class LoginPanel : BasePanel { private Button closebtn; private InputField usernameInput; private InputField passwordInput; private Button loginBtn; private Button registerBtn; private void Start() { closebtn = transform.Find("closebtn").GetComponent<Button>(); closebtn.onClick.AddListener(OnCloseClick); usernameInput = transform.Find("usernameinput").GetComponent<InputField>(); passwordInput = transform.Find("passwordinput").GetComponent<InputField>(); loginBtn = transform.Find("loginbtn").GetComponent<Button>(); registerBtn = transform.Find("registerbtn").GetComponent<Button>(); loginBtn.onClick.AddListener(OnLoginClick); registerBtn.onClick.AddListener(OnRegisterClick); } public override void OnEnter() { base.OnEnter(); gameObject.SetActive(true); if (closebtn == null) { closebtn = transform.Find("closebtn").GetComponent<Button>(); closebtn.onClick.AddListener(OnCloseClick); } transform.localScale = Vector3.zero; transform.localPosition = new Vector3(800,0,0); transform.DOScale(1, 0.4f); transform.DOLocalMove(Vector3.zero, 0.4f); usernameInput = transform.Find("usernameinput").GetComponent<InputField>(); passwordInput = transform.Find("passwordinput").GetComponent<InputField>(); loginBtn = transform.Find("loginbtn").GetComponent<Button>(); registerBtn = transform.Find("registerbtn").GetComponent<Button>(); loginRequest = GetComponent<LoginRequest>(); loginBtn.onClick.AddListener(OnLoginClick); registerBtn.onClick.AddListener(OnRegisterClick); } public void OnLoginClick() { string msg = string.Empty; if (string.IsNullOrEmpty(usernameInput.text)) { msg += "使用者名稱不能為空 "; } if (string.IsNullOrEmpty(passwordInput.text)) { msg += "密碼不能為空"; } if (msg != string.Empty) { uiMng.ShowMessage(msg);return; } } public void OnRegisterClick() { } public override void OnExit() { base.OnExit(); gameObject.SetActive(false); } public override void OnPause() { base.OnPause(); } public override void OnResume() { base.OnResume(); } public void OnCloseClick() { transform.DOScale(0, 0.4f); transform.DOLocalMove(new Vector3(800,0,0), 0.4f).OnComplete(()=> { uiMng.PopPanel(); }); } }
03-程式碼修改
在baserequest類中新增一個requestcode列舉型別變數,對應一個響應的requestcode型別,修改後的程式碼如下:
baserequest.cs:
using Common; using System.Collections; using System.Collections.Generic; using UnityEngine; public class BaseRequest : MonoBehaviour { protected RequestCode requestCode = RequestCode.None; protected ActionCode actionCode=ActionCode.None; protected GameFacade facade; // Use this for initialization public virtual void Awake () { GameFacade.Instance.AddRequest(actionCode,this); facade = GameFacade.Instance; } //請求的發起 public virtual void SendRequest() { } //請求的響應 public virtual void OnResponse(string data) { } public virtual void OnDestroy() { } }
04-在客戶端傳送登入請求
在共享工程common中,我們在ActionCode列舉中需要新增Login和Register列舉值,在RequestCode列舉中需要新增User列舉值,actioncode中的值列舉值對應了我們伺服器端傳送資料到客戶端的對應處理類以及客戶端傳送資料到伺服器端找到對應的方法, RequestCode用於客戶端傳送資料到伺服器端時用於找到對應的處理類,新增完後,替換掉unity工程中的common.dll,修改後的共享工程程式碼如下:
ActionCode.cs:
using System;
using System.Collections.Generic;
using System.Text;
namespace Common
{
public enum ActionCode
{
None,
Login,
Register,
}
}
RequestCode.cs:
using System;
using System.Collections.Generic;
using System.Text;
namespace Common
{
public enum RequestCode
{
None,
User,
}
}
接下來,需要建立一個用於處理登入請求的類LoginRequest類,這個類繼承自BaseRequest類,為登入請求類中的請求型別requestCode和事件型別變數actionCode進行賦值,然後建立一個傳送請求的方法用於傳送使用者名稱和密碼,最終發起請求需要通過clientmanager類來進行,為了降低耦合性,我們在gamefacade類中對傳送請求的方法進行轉接,所以需要我們在gamefacade中建立一個方法呼叫clientmanager中的發起請求方法,為了能夠使loginrequest類呼叫gamefacade,我們可以在baserequest類中得到gamefacade例項,然後在baserequest類中再過載一個sendrequest方法,這個方法與我們之前建立的此方法不同之處在於,此處的sendrequest方法我們傳入了一個字串型別的引數,引數表示的含義是資料資訊data,然後在loginrequest類就可以呼叫父類中的發起請求的方法。
為了方便請求與UI的互動,我們將loginrequest類直接繫結到loginpanel的面板上,然後在loginpanel類中得到loginrequest類的例項,然後在loginpanel類中就可以在點選登入按鈕後呼叫登入請求,向伺服器端發起登入請求。修改後的程式碼類如下所示:
LoginRequest.cs:
using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoginRequest : BaseRequest {
private void Start()
{
requestCode = RequestCode.User;
actionCode = ActionCode.Login;
}
public void SendRequest(string username,string password)
{
string data = username + "," + password;
base.SendRequest(data);
}
}
BaseRequest.cs:
using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseRequest : MonoBehaviour {
protected RequestCode requestCode = RequestCode.None;
protected ActionCode actionCode=ActionCode.None;
protected GameFacade facade;
// Use this for initialization
public virtual void Awake () {
GameFacade.Instance.AddRequest(actionCode,this);
facade = GameFacade.Instance;
}
public void SendRequest(string data)
{
facade.SendRequest(requestCode, actionCode, data);
}
//請求的發起
public virtual void SendRequest()
{
}
//請求的響應
public virtual void OnResponse(string data)
{
}
public virtual void OnDestroy()
{
}
}
GameFacade.cs:
using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameFacade : MonoBehaviour {
private static GameFacade _instance;
public static GameFacade Instance
{
get
{
return _instance;
}
}
private UIManager uiMng;
private AudioManager audioMng;
private PlayerManager playerMng;
private RequestManager requestMng;
private CameraManager cameraMng;
private ClientManager clientMng;
private void Awake()
{
if (_instance != null)
{
Destroy(this.gameObject);
return;
}
_instance = this;
}
// Use this for initialization
void Start() {
InitManager();
}
// Update is called once per frame
void Update() {
}
private void InitManager()
{
uiMng = new UIManager(this);
audioMng = new AudioManager(this);
playerMng = new PlayerManager(this);
requestMng = new RequestManager(this);
cameraMng = new CameraManager(this);
clientMng = new ClientManager(this);
uiMng.OnInit();
audioMng.OnInit();
playerMng.OnInit();
requestMng.OnInit();
cameraMng.OnInit();
clientMng.OnInit();
}
private void DestroyManager()
{
uiMng.OnDestory();
audioMng.OnDestory();
playerMng.OnDestory();
requestMng.OnDestory();
cameraMng.OnDestory();
clientMng.OnDestory();
}
private void OnDestroy()
{
DestroyManager();
}
public void AddRequest(ActionCode actionCode, BaseRequest request)
{
requestMng.AddRequest(actionCode, request);
}
public void RemoveRequest(ActionCode actionCode)
{
requestMng.RemoveRequest(actionCode);
}
public void HandleResponse(ActionCode actionCode, string data)
{
requestMng.HandleResponse(actionCode, data);
}
public void ShowMessage(string msg)
{
uiMng.ShowMessage(msg);
}
public void SendRequest(RequestCode requestCode, ActionCode actionCode, string data)
{
clientMng.SendRequest(requestCode, actionCode, data);
}
}
LoginPanel.cs:
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoginPanel : BasePanel
{
private Button closebtn;
private InputField usernameInput;
private InputField passwordInput;
private Button loginBtn;
private Button registerBtn;
private LoginRequest loginRequest;
private void Start()
{
closebtn = transform.Find("closebtn").GetComponent<Button>();
closebtn.onClick.AddListener(OnCloseClick);
usernameInput = transform.Find("usernameinput").GetComponent<InputField>();
passwordInput = transform.Find("passwordinput").GetComponent<InputField>();
loginBtn = transform.Find("loginbtn").GetComponent<Button>();
registerBtn = transform.Find("registerbtn").GetComponent<Button>();
loginRequest = GetComponent<LoginRequest>();
loginBtn.onClick.AddListener(OnLoginClick);
registerBtn.onClick.AddListener(OnRegisterClick);
}
public override void OnEnter()
{
base.OnEnter();
gameObject.SetActive(true);
if (closebtn == null)
{
closebtn = transform.Find("closebtn").GetComponent<Button>();
closebtn.onClick.AddListener(OnCloseClick);
}
transform.localScale = Vector3.zero;
transform.localPosition = new Vector3(800,0,0);
transform.DOScale(1, 0.4f);
transform.DOLocalMove(Vector3.zero, 0.4f);
usernameInput = transform.Find("usernameinput").GetComponent<InputField>();
passwordInput = transform.Find("passwordinput").GetComponent<InputField>();
loginBtn = transform.Find("loginbtn").GetComponent<Button>();
registerBtn = transform.Find("registerbtn").GetComponent<Button>();
loginRequest = GetComponent<LoginRequest>();
loginBtn.onClick.AddListener(OnLoginClick);
registerBtn.onClick.AddListener(OnRegisterClick);
}
public void OnLoginClick()
{
string msg = string.Empty;
if (string.IsNullOrEmpty(usernameInput.text))
{
msg += "使用者名稱不能為空 ";
}
if (string.IsNullOrEmpty(passwordInput.text))
{
msg += "密碼不能為空";
}
if (msg != string.Empty)
{
uiMng.ShowMessage(msg);return;
}
loginRequest.SendRequest(usernameInput.text, passwordInput.text);
}
public void OnRegisterClick()
{
}
public override void OnExit()
{
base.OnExit();
gameObject.SetActive(false);
}
public override void OnPause()
{
base.OnPause();
}
public override void OnResume()
{
base.OnResume();
}
public void OnCloseClick()
{
transform.DOScale(0, 0.4f);
transform.DOLocalMove(new Vector3(800,0,0), 0.4f).OnComplete(()=> { uiMng.PopPanel(); });
}
}
05-建立UserController、User和UserDAO做資料庫查詢校驗
經過以上幾個步驟,我們已經處理完了客戶端請求的發起,接下來需要對伺服器端進行登入請求的處理。
在伺服器端,我們需要建立一個對應的controller來處理登入請求,新建一個UserController類,繼承BaseController類,將basecontroller類中的請求型別變數設定許可權為protected,然後在usercontroller的初始化方法中對請求型別進行賦值,之後,我們需要在controller統一管理類controllermanager類中將usercontroller新增到字典中進行儲存。
在usercontroller中,我們需要建立事件處理方法,即登入方法Login,登入方法用於處理登入請求。每一個請求需要一些引數,我們在controllermanager類的請求處理方法中,我們將資料,客戶端,伺服器自身作為引數傳遞了過去,所以我們的登入方法也需要有這幾個引數。
在這裡我們需要發起對資料庫的請求,所以需要新增一個與資料庫對應的表類,以及一個數據庫的使用者操作類。首先,我們新建一個user類與資料庫的表的資訊相對應,然後,我們新建一個userdao類來對資料庫進行sql指令的增刪改查操作,修改後的類程式碼如下所示:
BaseController:
using Common;
using GameServer.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.Controller
{
abstract class BaseController
{
protected RequestCode requestCode = RequestCode.None;//設定請求型別
public RequestCode RequestCode
{
get
{
return requestCode;
}
}
//預設的處理方法
public virtual string DefaultHandle(string data,Client client,Server server)
{
return null;
}
}
}
UserController:
using Common;
using GameServer.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.Controller
{
class UserController:BaseController
{
public UserController()
{
requestCode = RequestCode.User;
}
public void Login(string data,Client client,Server server)
{
}
}
}
ControllerManager:
using Common;
using GameServer.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.Controller
{//用來管理controller
class ControllerManager
{
private Dictionary<RequestCode, BaseController> controllerDict = new Dictionary<RequestCode, BaseController>();//使用字典儲存有哪些controller
private Server server;
//構造方法
public ControllerManager(Server server)
{
this.server = server;
InitController();
}
//初始化方法
void InitController()
{
DefaultController defaultController = new DefaultController();
controllerDict.Add(defaultController.RequestCode,defaultController);
controllerDict.Add(RequestCode.User, new UserController());
}
//處理請求
public void HandleRequest(RequestCode requestCode,ActionCode actionCode,string data,Client client)
{
BaseController controller;
bool isGet = controllerDict.TryGetValue(requestCode, out controller);
if (isGet == false)
{
Console.WriteLine("無法得到requestcode:"+requestCode+"所對應的controller,無法處理請求");
return;
}
//通過反射得到
string methodName = Enum.GetName(typeof(ActionCode),actionCode);//得到方法名
MethodInfo mi= controller.GetType().GetMethod(methodName);//得到方法的資訊
if (mi == null)
{
Console.WriteLine("[警告]在controller【"+controller.GetType()+"】"+"中沒有對應的處理方法【"+methodName+"】");
return;
}
object[] parameters = new object[] { data,client,server};
object o= mi.Invoke(controller, parameters);//反射呼叫方法並將得到返回值
//如果返回值為空,則表示沒有得到,那麼就結束請求的進一步處理
if(string.IsNullOrEmpty(o as string))
{
return;
}
server.SendResponse(client,actionCode,o as string);//呼叫server類中的響應方法,給客戶端響應
}
}
}
User:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.Model
{
class User
{
public User(int id,string username,string password)
{
Id = id;
Username = username;
Password = password;
}
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
}
UserDAO:
using GameServer.Model;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.DAO
{
class UserDAO
{
//驗證使用者
public User VerifyUser(MySqlConnection conn, string username,string password)
{
MySqlDataReader reader=null;
try
{
MySqlCommand cmd = new MySqlCommand("select * from user where [email protected] and [email protected]", conn);
cmd.Parameters.AddWithValue("username", username);
cmd.Parameters.AddWithValue("pwd", password);
reader = cmd.ExecuteReader();
if (reader.Read())
{
int id = reader.GetInt32("id");
string name = reader.GetString("username");
string pwd = reader.GetString("password");
User user = new User(id, name, pwd);
return user;
}
else
{
return null;
}
}
catch (Exception e)
{
Console.WriteLine("在verifyuser的時候出現異常:"+e);
}
finally
{
reader.Close();
}
return null;
}
}
}
06-在伺服器端傳送登入的響應
前面我們完成了對資料庫中資料的操作函式,接下里,我們需要在usercontroller中完成對登入請求的響應。
在login方法中,我們首先需要將客戶端傳送過來的資料進行分割,得到字串陣列,然後得到通過userdao的返回值得到user變數,如果user為空,則表示沒有查詢到相應的結果,我們通過新建一個列舉型別returncode來對結果進行返回,所以我們還需要在共享工程common中新建一個returncode的列舉型別。需要注意的是,在呼叫userdao中的查詢方法時,需要得到mysqlconnection例項,所以我們需要在client中定義一個外界可訪問的mysqlconnection變數的get方法用於獲得此變數。修改後的程式碼類如下所示:
UserController:
using Common;
using GameServer.DAO;
using GameServer.Model;
using GameServer.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.Controller
{
class UserController:BaseController
{
private UserDAO userDAO=new UserDAO();
public UserController()
{
requestCode = RequestCode.User;
}
public string Login(string data,Client client,Server server)
{
string[] strs = data.Split(',');
User user= userDAO.VerifyUser(client.MySQLConn, strs[0], strs[1]);
if (user == null)
{
//Enum.GetName(typeof(ReturnCode), ReturnCode.Fail);
return ((int)ReturnCode.Fail).ToString();
}else
{
return ((int)ReturnCode.Success).ToString();
}
}
}
}
Client:
using Common;
using GameServer.Tool;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace GameServer.Servers
{//用來處理與客戶端的通訊問題
class Client
{
private Socket clientSocket;
private Server server;//持有一個server類的引用
private Message msg = new Message();
private MySqlConnection mysqlConn;//持有一個對資料庫的連線
public MySqlConnection MySQLConn
{
get
{
return mysqlConn;
}
}
public Client() { }
public Client(Socket clientSocket,Server server)
{
this.clientSocket = clientSocket;
this.server = server;
mysqlConn = ConnHelper.Connect();//建立於資料庫的連線
}
//開啟監聽
public void Start()
{
clientSocket.BeginReceive(msg.Data,msg.StartIndex, msg.RemainSizs, SocketFlags.None,ReceiveCallBack, null);
}
//接收監聽的回撥函式
private void ReceiveCallBack(IAsyncResult ar)
{
//做異常捕捉
try
{
int count = clientSocket.EndReceive(ar);//結束監聽,並返回接收到的資料長度
//如果count=0說明客戶端已經斷開連線,則直接關閉
if (count == 0)
{
Close();
}
msg.ReadMessage(count,OnProcessMessage);//對訊息的處理,進行訊息的解析
Start();//重新呼叫監聽函式
}
catch (Exception e)
{
Console.WriteLine(e);
//出現異常則退出
Close();
}
}
private void OnProcessMessage(RequestCode requestCode,ActionCode actionCode,string data)
{
server.HandleRequest(requestCode, actionCode, data, this);
}
private void Close()
{
ConnHelper.CloseConnection(mysqlConn);
if (clientSocket != null)
{
clientSocket.Close();
}
server.RemoveClient(this);
}
//向客戶端傳送資料
public void Send(ActionCode actionCode,string data)
{
byte[] bytes = Message.PackData(actionCode, data);
clientSocket.Send(bytes);
}
}
}
ReturnCode:
using System;
using System.Collections.Generic;
using System.Text;
namespace Common
{
public enum ReturnCode
{
Success,
Fail,
}
}
07-在客戶端處理登入的響應
在unity工程中,我們需要處理客戶端對登入的響應,在LoginRequest類中,我們重寫父類中的OnResonse方法,將伺服器端返回的結果進行解析並做出相應的處理。
首先,將returncode值轉成ReturnCode型別,然後在loginpanel中,我們定義一個對返回值進行相應處理的方法,然後,在LoginRequest類中,我們得到loginpanel並呼叫此方法。修改後的程式碼如下:
LoginRequest.cs:
using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoginRequest : BaseRequest {
private LoginPanel loginPanel;
private void Start()
{
loginPanel = GetComponent<LoginPanel>();
requestCode = RequestCode.User;
actionCode = ActionCode.Login;
}
public void SendRequest(string username,string password)
{
string data = username + "," + password;
base.SendRequest(data);
}
public override void OnResponse(string data)
{
ReturnCode returnCode = (ReturnCode)int.Parse(data);
loginPanel.OnLoginResponse(returnCode);
}
}
LoginPanel.cs:
using Common;
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoginPanel : BasePanel
{
private Button closebtn;
private InputField usernameInput;
private InputField passwordInput;
private Button loginBtn;
private Button registerBtn;
private LoginRequest loginRequest;
private void Start()
{
closebtn = transform.Find("closebtn").GetComponent<Button>();
closebtn.onClick.AddListener(OnCloseClick);
usernameInput = transform.Find("usernameinput").GetComponent<InputField>();
passwordInput = transform.Find("passwordinput").GetComponent<InputField>();
loginBtn = transform.Find("loginbtn").GetComponent<Button>();
registerBtn = transform.Find("registerbtn").GetComponent<Button>();
loginRequest = GetComponent<LoginRequest>();
loginBtn.onClick.AddListener(OnLoginClick);
registerBtn.onClick.AddListener(OnRegisterClick);
}
public override void OnEnter()
{
base.OnEnter();
gameObject.SetActive(true);
if (closebtn == null)
{
closebtn = transform.Find("closebtn").GetComponent<Button>();
closebtn.onClick.AddListener(OnCloseClick);
}
transform.localScale = Vector3.zero;
transform.localPosition = new Vector3(800,0,0);
transform.DOScale(1, 0.4f);
transform.DOLocalMove(Vector3.zero, 0.4f);
usernameInput = transform.Find("usernameinput").GetComponent<InputField>();
passwordInput = transform.Find("passwordinput").GetComponent<InputField>();
loginBtn = transform.Find("loginbtn").GetComponent<Button>();
registerBtn = transform.Find("registerbtn").GetComponent<Button>();
loginRequest = GetComponent<LoginRequest>();
loginBtn.onClick.AddListener(OnLoginClick);
registerBtn.onClick.AddListener(OnRegisterClick);
}
public void OnLoginClick()
{
string msg = string.Empty;
if (string.IsNullOrEmpty(usernameInput.text))
{
msg += "使用者名稱不能為空 ";
}
if (string.IsNullOrEmpty(passwordInput.text))
{
msg += "密碼不能為空";
}
if (msg != string.Empty)
{
uiMng.ShowMessage(msg);return;
}
loginRequest.SendRequest(usernameInput.text, passwordInput.text);
}
public void OnRegisterClick()
{
}
public override void OnExit()
{
base.OnExit();
gameObject.SetActive(false);
}
public override void OnPause()
{
base.OnPause();
}
public override void OnResume()
{
base.OnResume();
}
public void OnCloseClick()
{
transform.DOScale(0, 0.4f);
transform.DOLocalMove(new Vector3(800,0,0), 0.4f).OnComplete(()=> { uiMng.PopPanel(); });
}
public void OnLoginResponse(ReturnCode returnCode)
{
if (returnCode == ReturnCode.Success)
{
}
else
{
uiMng.ShowMessage("密碼或使用者名稱錯誤,無法登入,請重新輸入");
}
}
}
08-測試登入流程
首先,我們需要將伺服器端啟動起來,在啟動時需要我們在主函式中呼叫server.start()方法,這是伺服器端初始化的方法。
遇到的問題:
1、在伺服器端的server類中,我們定義了一個clientList的集合,這個集合需要進行初始化
private List<Client> clientList=new List<Client>();//用來儲存所有連線的客戶端
2、伺服器端啟動之後啟動客戶端,無法看到連線情況,這並不是出現了問題,而是沒有做相應的輸出,所以我們可以在啟動函式中輸出伺服器端啟動成功和客戶端連線成功的提示。
server類中進行的提示修改:
//建立連線
public void Start()
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(ipEndPoint);//繫結ip
serverSocket.Listen(0);//設定監聽,為0表示不限制連線數
Console.WriteLine("伺服器端啟動");////////////新增的提示/////////////
serverSocket.BeginAccept(AcceptCallBack, null);//開始接收客戶端連線
}
//建立接收連線的回撥函式
private void AcceptCallBack(IAsyncResult ar)
{
Socket clientSocket = serverSocket.EndAccept(ar);//接收到連線並將返回的客戶端socket進行得到
Client client = new Client(clientSocket, this);//建立一個client類,用來管理一個與客戶端的連線
client.Start();
Console.WriteLine("客戶端連線成功");//////////新增的提示//////////
clientList.Add(client);//將此客戶端新增到list集合中
}
3、在unity工程中報錯,不能找到對應的reqeust,查詢後發現是由於Awake方法執行比start方法早,所以還沒有給requestcode和actioncode賦上新值。修改的方法是在loginrequest類中重寫awake方法,然後將所有在start方法中的方法放在base.awake()這句程式碼的上面,這樣就可以在賦完值之後再呼叫父類中的awake方法了。
4、在unity工程中報錯,原因是我們使用了多執行緒,所以在呼叫資訊顯示的方法時不在同一個執行緒會出錯,解決辦法是在messagepanel中定義一個非同步的資訊顯示方法,然後用update方法來實時判斷是否需要顯示資訊,然後在uimanager中轉發呼叫messagepanel中的非同步訊息顯示方法,最後在loginpanel中呼叫這個方法來顯示我們的資訊即可。修改後的程式碼如下所示:
LoginPanel.cs:
using Common;
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoginPanel : BasePanel
{
private Button closebtn;
private InputField usernameInput;
private InputField passwordInput;
private Button loginBtn;
private Button registerBtn;
private LoginRequest loginRequest;
private void Start()
{
closebtn = transform.Find("closebtn").GetComponent<Button>();
closebtn.onClick.AddListener(OnCloseClick);
usernameInput = transform.Find("usernameinput").GetComponent<InputField>();
passwordInput = transform.Find("passwordinput").GetComponent<InputField>();
loginBtn = transform.Find("loginbtn").GetComponent<Button>();
registerBtn = transform.Find("registerbtn").GetComponent<Button>();
loginRequest = GetComponent<LoginRequest>();
loginBtn.onClick.AddListener(OnLoginClick);
registerBtn.onClick.AddListener(OnRegisterClick);
}
public override void OnEnter()
{
base.OnEnter();
gameObject.SetActive(true);
if (closebtn == null)
{
closebtn = transform.Find("closebtn").GetComponent<Button>();
closebtn.onClick.AddListener(OnCloseClick);
}
transform.localScale = Vector3.zero;
transform.localPosition = new Vector3(800,0,0);
transform.DOScale(1, 0.4f);
transform.DOLocalMove(Vector3.zero, 0.4f);
usernameInput = transform.Find("usernameinput").GetComponent<InputField>();
passwordInput = transform.Find("passwordinput").GetComponent<InputField>();
loginBtn = transform.Find("loginbtn").GetComponent<Button>();
registerBtn = transform.Find("registerbtn").GetComponent<Button>();
loginRequest = GetComponent<LoginRequest>();
loginBtn.onClick.AddListener(OnLoginClick);
registerBtn.onClick.AddListener(OnRegisterClick);
}
public void OnLoginClick()
{
string msg = string.Empty;
if (string.IsNullOrEmpty(usernameInput.text))
{
msg += "使用者名稱不能為空 ";
}
if (string.IsNullOrEmpty(passwordInput.text))
{
msg += "密碼不能為空";
}
if (msg != string.Empty)
{
uiMng.ShowMessage(msg);return;
}
loginRequest.SendRequest(usernameInput.text, passwordInput.text);
}
public void OnRegisterClick()
{
}
public override void OnExit()
{
base.OnExit();
gameObject.SetActive(false);
}
public override void OnPause()
{
base.OnPause();
}
public override void OnResume()
{
base.OnResume();
}
public void OnCloseClick()
{
transform.DOScale(0, 0.4f);
transform.DOLocalMove(new Vector3(800,0,0), 0.4f).OnComplete(()=> { uiMng.PopPanel(); });
}
public void OnLoginResponse(ReturnCode returnCode)
{
if (returnCode == ReturnCode.Success)
{
}
else
{
uiMng.ShowMessageSync("密碼或使用者名稱錯誤,無法登入,請重新輸入");
}
}
}
MessagePanel.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MessagePanel : BasePanel
{
private Text text;
private float showTime=1f;
private string message = null;
public override void OnEnter()
{
base.OnEnter();
text = GetComponent<Text>();
text.enabled = false;
uiMng.InjectMsgPanel(this);
}
private void Update()
{
if (message != null)
{
ShowMessage(message);
}
}
public override void OnExit()
{
base.OnExit();
}
public override void OnPause()
{
base.OnPause();
}
public override void OnResume()
{
base.OnResume();
}
public void ShowMessage(string msg)
{
text.CrossFadeAlpha(1, 0.2f, true);
text.text = msg;
text.enabled = true;
Invoke("Hide", showTime);
}
public void ShowMessageSync(string msg)
{
message = msg;
}
private void Hide()
{
text.CrossFadeAlpha(0, showTime, true);
}
}
UIManager.cs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class UIManager:BaseManager {
///
/// 單例模式的核心
/// 1,定義一個靜態的物件 在外界訪問 在內部構造
/// 2,構造方法私有化
//private static UIManager _instance;
//public static UIManager Instance
//{
// get
// {
// if (_instance == null)
// {
// _instance = new UIManager();
// }
// return _instance;
// }
//}
public override void OnInit()
{
base.OnInit();
PushPanel(UIPanelType.Message);
PushPanel(UIPanelType.Start);
}
private Transform canvasTransform;
private Transform CanvasTransform
{
get
{
if (canvasTransform == null)
{
canvasTransform = GameObject.Find("Canvas").transform;
}
return canvasTransform;
}
}
private Dictionary<UIPanelType, string> panelPathDict;//儲存所有面板Prefab的路徑
private Dictionary<UIPanelType, BasePanel> panelDict;//儲存所有例項化面板的遊戲物體身上的BasePanel元件
private Stack<BasePanel> panelStack;
private MessagePanel msgPanel;
public UIManager(GameFacade facade) : base(facade)
{
ParseUIPanelTypeJson();
}
/// <summary>
/// 把某個頁面入棧, 把某個頁面顯示在介面上
/// </summary>
public void PushPanel(UIPanelType panelType)
{
if (panelStack == null)
panelStack = new Stack<BasePanel>();
//判斷一下棧裡面是否有頁面
if (panelStack.Count > 0)
{
BasePanel topPanel = panelStack.Peek();
topPanel.OnPause();
}
BasePanel panel = GetPanel(panelType);
panel.OnEnter();
panelStack.Push(panel);
}
/// <summary>
/// 出棧 ,把頁面從介面上移除
/// </summary>
public void PopPanel()
{
if (panelStack == null)
panelStack = new Stack<BasePanel>();
if (panelStack.Count <= 0) return;
//關閉棧頂頁面的顯示
BasePanel topPanel = panelStack.Pop();
topPanel.OnExit();
if (panelStack.Count <= 0) return;
BasePanel topPanel2 = panelStack.Peek();
topPanel2.OnResume();
}
/// <summary>
/// 根據面板型別 得到例項化的面板
/// </summary>
/// <returns></returns>
private BasePanel GetPanel(UIPanelType panelType)
{
if (panelDict == null)
{
panelDict = new Dictionary<UIPanelType, BasePanel>();
}
//BasePanel panel;
//panelDict.TryGetValue(panelType, out panel);//TODO
BasePanel panel = panelDict.TryGet(panelType);
if (panel == null)
{
//如果找不到,那麼就找這個面板的prefab的路徑,然後去根據prefab去例項化面板
//string path;
//panelPathDict.TryGetValue(panelType, out path);
string path = panelPathDict.TryGet(panelType);
GameObject instPanel = GameObject.Instantiate(Resources.Load(path)) as GameObject;
instPanel.transform.SetParent(CanvasTransform,false);
instPanel.GetComponent<BasePanel>().UIMng = this;
panelDict.Add(panelType, instPanel.GetComponent<BasePanel>());
return instPanel.GetComponent<BasePanel>();
}
else
{
return panel;
}
}
[Serializable]
class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
}
private void ParseUIPanelTypeJson()
{
panelPathDict = new Dictionary<UIPanelType, string>();
TextAsset ta = Resources.Load<TextAsset>("UIPanelType");
UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(ta.text);
foreach (UIPanelInfo info in jsonObject.infoList)
{
//Debug.Log(info.panelType);
panelPathDict.Add(info.panelType, info.path);
}
}
public void InjectMsgPanel(MessagePanel msgPanel)
{
this.msgPanel = msgPanel;
}
public void ShowMessage(string msg)
{
if (msg == null)
{
Debug.Log("無法顯示提示資訊,msgpanel為空");
return;
}
msgPanel.ShowMessage(msg);
}
public void ShowMessageSync(string msg)
{
if (msg == null)
{
Debug.Log("無法顯示提示資訊,msgpanel為空");
return;
}
msgPanel.ShowMessageSync(msg);
}
/// <summary>
/// just for test
/// </summary>
//public void Test()
//{
// string path ;
// panelPathDict.TryGetValue(UIPanelType.Knapsack,out path);
// Debug.Log(path);
//}
}
最終的執行結果:
相關推薦
unity網路實戰開發(叢林戰爭)-正式開發階段(016-資料庫設計以及登入處理)
使用工具:VS2017,unity3d使用語言:c#作者:Gemini_xujian參考:siki老師-《叢林戰爭》視訊教程上一篇文章中,已經完成了遊戲場景與開始介面UI的搭建,接下來將對資料庫和登入請求響應等操作進行處理。01-設計資料庫表(使用者表和戰績表)首先,我們需要
unity網路實戰開發(叢林戰爭)-正式開發階段(015-遊戲場景及開始介面UI搭建)
使用工具:VS2017,unity3d使用語言:c#作者:Gemini_xujian參考:siki老師-《叢林戰爭》視訊教程上一篇文章中,我已經完成了遊戲客戶端與伺服器端的初步連線,接下來將開始進行遊戲場景與開始介面UI的搭建。01-控制場景的視野漫遊動畫作為選單介面背景首先
unity網路實戰開發(叢林戰爭)-前期知識準備(003-開發伺服器端的傳送資料和接收資料)
使用工具:VS2015使用語言:c#作者:Gemini_xujian參考:siki老師-《叢林戰爭》視訊教程繼上一篇文章內容,這節課講解一下伺服器端的傳送資料和接收資料。上篇文章完成了ip和埠號的繫結,接下來,我們首先需要監聽埠並接收客戶端的連線serverSocket.Li
unity網路實戰開發(叢林戰爭)-前期知識準備(011-c#連線資料庫並實現增刪改查以及sql注入問題)
使用工具:VS2015,Mysql使用語言:c#作者:Gemini_xujian參考:siki老師-《叢林戰爭》視訊教程繼上一篇文章內容,這節課講解一下資料庫的前期連線準備以及通過c# 實現資料庫的增刪改擦操作。首先你需要自行安裝Mysql以及它的workbench元件。然後
unity網路實戰開發(叢林戰爭)-正式開發階段(018-聲音管理器模組的完善)
使用工具:VS2017,unity3d 使用語言:c# 作者:Gemini_xujian 參考:siki老師-《叢林戰爭》視訊教程 上一篇文章中,已經完成了註冊事件的處理,接下來將完善聲音管理器模組。 01-開發聲音管理器 為了使遊戲執行起來更加富有活力,接下來,
unity網路實戰開發(叢林戰爭)-正式開發階段(013-遊戲伺服器端框架搭建)
使用工具:VS2015使用語言:c#作者:Gemini_xujian參考:siki老師-《叢林戰爭》視訊教程繼上一篇文章內容,這節課講解一下游戲伺服器端的開發。01-專案目錄結構建立:首先開啟VS並建立一個c#控制檯應用程式專案,起名為“遊戲伺服器端”,建立好後,右鍵專案-&
資料庫操作之增加資料(叢林戰爭專案)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using MySql.Data.MyS
遊戲開發,叢林戰爭3
34.訊息面板的顯示 首先的話,我們建立我們的MessagePanel指令碼,這裡我們提供了建立,顯示,隱藏三個方法,並且的話我們這個面板指令碼的控制是交給Uimanager進行管理的 publicclassMessagePanel : BasePanel { pr
jQuery的簡單用法(jQuery的簡介,選擇器,屬性和css,文檔處理)
isa isp 3.6 大於 doc ie6 tab append 需要 一、jQuery簡介 1.1. JS庫 JavaScript 庫封裝了很多預定義的對象和實用函數。能幫助使用者建立有高難度交互客戶端頁面, 並且兼容各大瀏覽器。 1.2. 當前流行的 JavaSc
資料庫系統概念(機械工業出版社,第七版)複習——第六章:資料庫設計和E-R模型
E-R模型 實體-聯絡模型:Entity-Relationship Model E-R圖要點 實體(Entity) 客觀存在並可相互區分的事物叫實體(唯一標識)。 實體集(Entity Set) 是具有相同型別及共享相同性質(屬性)的實體集合。如全體學生。組成實體集的各實
PNG透明圖片疊加(Opencv實現,包括旋轉縮放以及邊界處理)
疊加效果原圖疊加圖片大致實現了一個四通道的PNG向量圖疊加到一個三通道的圖片上,函式如下:bool 疊加(cv::Mat &dst, cv::Mat &src,double scale = 1.0, //整體透明度double size = 1.0,//圖片縮
PHP購物網站(含購物車、全部原始碼、資料庫設計表及其原始碼)
這是我在研究生期間,老師要求做的一個類似原始淘寶網的購物網站, 因為不會PHP,為了寫這個作業而新學的。 做這個網站用了兩週時間,在此把這個小專案做一個總結。 這個小專案做的時間非常趕,一共兩週,實際有效時間只有10天,中間還在忙其他的事。所以有很多不足之
Vue從入門到精通(5)--第四階段(上):Vue入門及Vue-cli上手教程
第四階段 vue2.0語法 vue2.0+webpack搭建開發環境 官方腳手架vue-cli Vue的作者尤雨溪大神寫了一篇《新手向:Vue 2.0 的建議學習順序》來介紹Vue及相關知識點的學習路線,主要列出了知識點概要,如果你覺得略
網路遊戲《叢林戰爭》開發與學習之(二):粘包分包現象以及服務端解析資料
1. 粘包和分包 粘包和分包是利用Socket在TCP協議下內部的優化機制。粘包指的是傳送資料比較頻繁,但資料量較少,此時客戶端不會直接將資料包傳送給伺服器,而是會與其它的資料包進行一個結合,例如遊戲中的位置資訊就是屬於頻繁傳送但資料量小的資訊,此時如果每條資料都S
網路遊戲《叢林戰爭》開發與學習之(一):網路程式設計的基礎知識
《叢林戰爭》是一款完整的網路遊戲案例,運用U3D開發客戶端,Socket開發服務端,其中涉及到了網路程式設計、資料庫和Unity的功能實現,之前通過U3D開發了一個單機遊戲《黑暗之光》,並沒有涉及網路程式設計的知識,通過《叢林戰爭》這個完整的遊戲,系統性地學習網路程式設計,並
DDD實戰進階第一波(五):開發一般業務的大健康行業直銷系統(實現產品上下文領域層)
討論 clas 基本 ted ctc decimal nco protect pan 從這篇文章開始,我們根據前面的DDD理論與DDD框架的約束,正式進入直銷系統案例的開發。 本篇文章主要講產品上下文中的領域層的主要實現,先簡單講下業務方面的需求:產品SPU與產品SKU,產
Flask框架的學習與實戰(一):開發環境搭建
進行 read 模型 clas tar pychar html itl .html Flask是一個使用 Python 編寫的輕量級 Web 應用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎則使用 Jinja2。很多功能的實現都參考了django框架。由於項
(Flask Web開發:基於Python的Web應用開發實戰)------學習筆記(第2章)
第2章 程式的基本結構 本章將帶你瞭解 Flask 程式各部分的作用,編寫並執行第一個 Flask Web 程式。 2.1 初始化 所有 Flask 程式都必須建立一個程式例項,程式例項是 Flask 類的物件。 Web 伺服器使用一種名為 Web 伺服器閘
《叢林戰爭》Unity5.6直接利用C# Socket/TCP開發網路遊戲視訊教程
目錄 ├─1.榜首部分 Cocos2d-x 下載和裝置.mp4 ├─10.第二部分 大型地圖.mp4 ├─11.第二部分 Cocos2d-x 繪圖原理及優化(一).wmv.mp4 ├─12.第二部分 Cocos2d-x 繪圖原理及優化(二).mp4 ├
Unity Steam_VR 開發工具外掛 VRTK自帶案例分析(第二部分)
轉載至https://www.cnblogs.com/zerotoinfinity/p/6485556.html DEMO 025_Controls_Overview 互動控制器: 真實版 UI 元素。所有型別的控制器都繼承自VRTK_Contro