遊戲開發,叢林戰爭3
34.訊息面板的顯示
首先的話,我們建立我們的MessagePanel指令碼,這裡我們提供了建立,顯示,隱藏三個方法,並且的話我們這個面板指令碼的控制是交給Uimanager進行管理的
publicclassMessagePanel : BasePanel {
private Texttext;
privatefloat showTime = 1f;
publicoverridevoid OnEnter()
{ //這個是用來建立訊息面板的
base.OnEnter();
text = this.GetComponent<Text>();
text.enabled = false
uiMng.InjectMsg(this);
}
publicvoid ShowMessage(string data)
{
text.text = data;
//將alpha值重新設定為1
text.enabled = true;
text.color = Color.white;
//一秒種後隱藏這個方法
Invoke("HideMessage", showTime);
}
publicvoid HideMessage()
{
//這個是用來修改text的Alpha值使得它的顯示為隱藏
text.CrossFadeAlpha(0, 1, false);
}
}
因為所有的Ui面板都是用UiManager進行管理的,因此我們提供在父類提供一個方法來獲取到Uimanager
public UIManager UIManager
{
//提供一個set方法可以進行賦值
set { uiMng= value; }
}
然後在這個例項化面板的方法裡,當我們獲得到對應的面板的時候,就會對它進行賦值,讓UiManager進行管理
privateBasePanel GetPanel(UIPanelType panelType)
{
if(panelDict == null)
{
panelDict = new Dictionary<UIPanelType,BasePanel>();
}
//BasePanel panel;
//panelDict.TryGetValue(panelType, outpanel);//TODO
BasePanel panel = panelDict.TryGet(panelType);
if (panel== null)
{
//如果找不到,那麼就找這個面板的prefab的路徑,然後去根據prefab去例項化面板
//stringpath;
//panelPathDict.TryGetValue(panelType,out path);
string path = panelPathDict.TryGet(panelType);
GameObject instPanel =GameObject.Instantiate(Resources.Load(path)) as GameObject;
instPanel.transform.SetParent(CanvasTransform,false);
//例項化對應面板後我們要給這個面板提供UiManager這個值
instPanel.GetComponent<BasePanel>().UIManager = this;
panelDict.Add(panelType,instPanel.GetComponent<BasePanel>());
return instPanel.GetComponent<BasePanel>();
}
然後我們在UIManager裡提供兩個方法,來引用和操控我們的MessagePanel
publicvoid ShowMessage(string msg)
{
if(messagePanel == null)
{
Debug.Log("無法顯示該面板");
return;
}
messagePanel.ShowMessage(msg);
}
privatevoid HideMessage()
{
messagePanel.HideMessage();
}
publicvoidInjectMsg(MessagePanel messagePanel)
{
//當面板被建立的時候,注入這個Panel
messagePanel = this.messagePanel;
}
如果其他的模組也想要用這個提示錯誤資訊的話,我們只要在GameFacade裡提供這樣一個介面方法來呼叫Uimanager裡的ShowMessage即可
publicvoid ShowMessage(string msg)
{
uIManager.ShowMessage(msg);
}
35.這裡我們來開發我們的UI介面
這裡實現的是點選登入彈出登入框,這個的話,首先,我們用UIManager來創建出我們的訊息面板和登入面板
publicoverridevoid OnInit()
{
base.OnInit();
PushPanel(UIPanelType.Start);
PushPanel(UIPanelType.Message);
}
然後的話,我們修改我們的StartPanel,給登入註冊上點選建立登入頁面的按鈕
publicclassStartPanel : BasePanel {
private ButtonloginButton;
privateAnimator btnAnimator;
publicoverridevoid OnEnter()
{
base.OnEnter();
gameObject.SetActive(true);
loginButton = transform.Find("LoginButton").GetComponent<Button>();
btnAnimator = transform.Find("LoginButton").GetComponent<Animator>();
loginButton.onClick.AddListener(OnClick);
}
publicvoid OnClick()
{
uiMng.PushPanel(UIPanelType.Login);
}
然後給我們的登入頁面新增一個關閉功能即可,這裡我們使用DoTween控制頁面的切換
publicclassLoginPanel : BasePanel {
private ButtoncloseButton;
// Use this for initialization
publicoverridevoid OnEnter()
{
base.OnEnter();
gameObject.SetActive(true);
transform.DOScale(1, 0.4f);
transform.DOLocalMove( new Vector3(35,0,0), 0.4f);
closeButton = transform.Find("Exit").GetComponent<Button>();
closeButton.onClick.AddListener(OnCloseClick);
}
publicvoid OnCloseClick()
{
uiMng.PopPanel();
transform.DOScale(0, 0.4f);
//transform.DOLocalMove(new Vector3(1000,0, 0), 0.4f).OnComplete(()=>uiMng.PopPanel());
}
publicoverridevoid OnExit()
{
base.OnExit();
gameObject.SetActive(false);
}
}
36.建立兩個資料表用來管理我們的資訊
一個使用者表,包含id,使用者名稱,密碼
一個戰機表,包含id,使用者id,總局數,勝利局數
這裡我們建立這樣兩個資料表,戰績表的使用者id,作為使用者表id的外來鍵相關聯
37.登入面板檢測你是否輸入了使用者名稱或者密碼
首先,我們獲得一下兩個輸出域
userIf = transform.Find("userName/InputField").GetComponent<InputField>();
passwordIf = transform.Find("password/InputField").GetComponent<InputField>();
然後我們註冊一下登入按鈕
loginButton = transform.Find("LoginButton").GetComponent<Button>();
loginButton.onClick.AddListener(OnLoginClick);
根據輸出域判斷訊息並且決定訊息面板顯示什麼資訊
publicvoid OnLoginClick()
{
string msg = "";
if (string.IsNullOrEmpty(userIf.text))
{
msg += "使用者名稱為空";
}
if (string.IsNullOrEmpty(passwordIf.text))
{
msg += "密碼為空";
}
if (msg !=null)
{
uiMng.ShowMessage(msg);
return;
}
Debug.Log(msg + "登入按鈕被點選了");
}
1. 修改讓requestCode指定對應的ActionCode
這裡我們要知道,我們是通過RequestCode來找到對應的Controller,通過ActionCode區別Request
1. 首先的話,我們修改BaseRequest,子類繼承的時候都要修改他們的值
publicclassBaseRequest : MonoBehaviour {
protectedRequestCode requestCode = RequestCode.None;
protected ActionCodeactionCode = ActionCode.None;
2. 修改RequestManager,我們是通過RequestCode獲得對應的ActionCode
publicclassBaseRequest : MonoBehaviour {
protected RequestCoderequestCode = RequestCode.None;
protectedActionCode actionCode = ActionCode.None;
3. 修改GameFacade中對應的獲得Request的方法
publicvoid AddRequest(ActionCode actionCode,BaseRequestbaseRequest)
{
requestManager.AddRequest(actionCode,baseRequest);
}
publicvoid RemoveRequest(ActionCode actionCode)
{
requestManager.RemoveRequest(actionCode);
}
publicvoid HandleResponse(ActionCode actionCode, string data)
{
requestManager.HandleResponse(actionCode, data);
}
4. 修改伺服器端的各種RequestCode,這裡的話只要根據報錯改就可以了,還是很簡單的,目的就是每個RequestCode用ActionCode區分
2. 客戶端向伺服器端發起登入請求
首先,我們要給RequestCode和ActionCode提供幾個code
publicenumRequestCode
{
None,
User
}
publicenumActionCode
{
None,
Login,
Regist
}
然後我們重新生成一下,把dll放到plugins下,這個一般是提前設計好,因為是案例,所以會將框架修改一下
這裡的話我們做的是客戶端傳送登入請求,於是我們建立一個LoginRequest
void Start()
{
//指定好LoginRequest中的requestCode和actionCode
requestCode = RequestCode.User;
actionCode = ActionCode.Login;
}
publicvoid SendRequest(string username,string password)
{
string data =username + "," +password;
base.SendRequest(data);
}
指定好它對應的requestCode和ActionCode,然後呼叫ClientManager裡的SendMessage方法,這個方法的呼叫我們通過GameFacade作為中介,而GameFacade的持有,我們放在父類BaseRequest,這裡還要修改一下GameFacade,使它持有一下ClientManager裡的SendMessage,這裡程式碼就不貼了,和之前的一樣
protectedGameFacade gameFacade;
publicvirtualvoid Awake()
{
GameFacade._instance.AddRequest(actionCode, this);
gameFacade = GameFacade._instance;
}
3. 新增使用者的Dao層和Model層來進行校驗
在Model層裡我們給它提供使用者屬性
namespace遊戲伺服器.Model
{
classUser
{
//這裡我們設定它的幾個屬性
public User(int id,string username,string password)
{
this.id = id;
this.username =username;
this.password =password;
}
publicint id { get; set; }
publicstring username { get; set; }
publicstring password { get; set; }
}
}
在Dao層的話,我們提供它的解析方法
using System;
usingSystem.Collections.Generic;
using System.Linq;
usingSystem.Text;
usingSystem.Threading.Tasks;
usingMySql.Data.MySqlClient;
using遊戲伺服器.Model;
namespace遊戲伺服器.Dao
{
classUserDao
{
//提供校驗方法,一個model對應一個Dao
public UserVerifyUser(MySqlConnection conn,string username,string password)
{
MySqlCommand cmd = new MySqlCommand("select * from user where [email protected] [email protected]", conn);
//插值作為連線查詢
MySqlDataReader mySqlDataReader = null;
try
{
mySqlDataReader =cmd.ExecuteReader();
cmd.Parameters.AddWithValue("username",username);
cmd.Parameters.AddWithValue("password",password);
if (mySqlDataReader.Read())
{
int id = mySqlDataReader.GetInt32("id");
User user = new User(id, username,password);
return user;
}
else
{
returnnull;
}
}
catch(Exception e)
{
Console.WriteLine("讀取使用者資訊出現異常" + e);
}
finally
{
mySqlDataReader.Close();
}
returnnull;
}
}
建立後我們在Controller層進行控制Dao層方法的呼叫即可
4. 讓伺服器端對我們的客戶端登入請求做出響應
首先我們在Common類裡提供一個列舉型別的方法判斷是否正確返回資訊
publicenumReturnCode
{
Success,
Fail
}
然後我們回到我們的UserController裡判斷我們是否登入成功
首先我們要用spilt方法分割使用者名稱和密碼,再用上一節寫的方法判斷是不是正確的使用者名稱和密碼,再返回對應的判斷即可,這裡要獲取conn,要給Client提供一個構造方法獲取,這裡的話就不寫了
publicstring Login(string data,Clientclient,Sever server)
{
string[] str = data.Split(',');
User user =userDao.VerifyUser(client.MysqlConn, str[0], str[1]);
if (user == null)
{
return ((int)(ReturnCode.Fail)).ToString();
}
else
{
return ((int)(ReturnCode.Success)).ToString();
}
}
5. 客戶端響應伺服器做出的迴應
這裡我們回到unity客戶端,這裡的話,伺服器傳送了資訊,需要客戶端做出響應,我們在LoginRequest對它做出響應,將伺服器返回的ReturnCode進行轉型,然後呼叫LoginPanel對UI進行反應
publicoverridevoid OnResponse(string data)
{
ReturnCode returnCode = (ReturnCode)(int.Parse(data));
loginPanel.OnResponse(returnCode);
}
這裡的OnResponse,會對你的returnCode進行反應,當然前提是連線了資料庫
publicvoidOnResponse(ReturnCode returnCode )
{
if(returnCode == ReturnCode.Success)
{
//判斷登入成功,進入房間列表
}
else
{
uiMng.ShowMessage("登入失敗,使用者名稱或者密碼不正確");
}
}
6. 測試登入效果
在測試登入效果的時候,不得不說,我碰到了很多的障礙,這也是之前沒有debug的原因,這裡講述一下
1. 在loginRequest中呼叫MessagePanel下的ShowMessage時,因為是非同步呼叫,所以無法修改,解決辦法嘛,就是呼叫Unity自帶的Update方法進行傳值,
publicvoid Update()
{
//因為這個BasePanel是繼承自MonBehaviour,所以我們能呼叫Update方法
if(message != null)
{
ShowMessage(message);
message = null;
}
}
publicvoid ShowMessage(string data)
{
//將alpha值重新設定為1
text.enabled = true;
text.text = data;
text.CrossFadeAlpha(1, 0.2f, false);
//一秒種後隱藏這個方法
Invoke("HideMessage", showTime);
}
publicvoid ShowMessageSync(string msg)
{
//非同步呼叫顯示資訊的方法,因為想要修改unity元件是不能直接修改
message = msg;
1. }
2. 這個是之前的程式碼問題,我在client裡面回撥資訊時候,寫錯了clientSocket的傳值方式
我把clientSocket=this.clientSocket寫成了this.clientSocket=clientSocket,不得不說,這一塊造成了很大的困擾,因為Bug很隱晦,看了很久,所幸最後還是找出來了,這裡提示了我修改的重要性,當然也因此重溫了一遍伺服器和客戶端的資訊派發機制
3. 接下來就很簡單了,我們手動給資料庫新增一條使用者名稱密碼做校驗就行,當然記得select資料的時候要先注入值再查詢,之前這一塊沒有做出來,導致Bug,這裡也標記一下
4. 那麼到這裡,我們的登入功能也就算完成了
7. 註冊頁面的一些Ui調整
這裡的話比較簡單,就是一個彈出頁面的操作,我就貼一下程式碼了
publicclassRegistPanel : BasePanel {
private ButtoncloseButton;
publicoverridevoid OnEnter()
{
base.OnEnter();
this.gameObject.SetActive(true);
transform.DOScale(1, 0.4f);
closeButton = transform.Find("Exit").GetComponent<Button>();
closeButton.onClick.AddListener(OnCloseButtonClick);
}
publicvoidOnCloseButtonClick()
{
transform.DOScale(0, 0.4f);
uiMng.PopPanel();
}
publicoverridevoid OnExit()
{
base.OnExit();
this.gameObject.SetActive(false);
}
}
8. 傳送資訊到伺服器端
這裡的話我們建立一個RegisterRequest來處理註冊的請求
也是持有我們的註冊頁面,然後傳送請求,根據ReturnCode判斷是否正確
publicclassRegisterRequest : BaseRequest {
privateRegistPanel registPanel;
publicoverridevoid Awake()
{
//指定好LoginRequest中的requestCode和actionCode
requestCode = RequestCode.User;
actionCode = ActionCode.Regist;
registPanel = GetComponent<RegistPanel>();
base.Awake();
}
publicvoid SendRequest(string username, string password)
{
string data =username + "," +password;
base.SendRequest(data);
}
publicoverridevoid OnResponse(string data)
{
ReturnCode returnCode = (ReturnCode)(int.Parse(data));
}
然後我們修改一下我們的RegisterPanel做註冊校驗和傳送資料
publicvoid Start()
{
userInfo = transform.Find("userName/InputField/Text").GetComponent<Text>();
passwordInfo = transform.Find("password/InputField/Text").GetComponent<Text>();
rePasswordInfo = transform.Find("RePassword/InputField/Text").GetComponent<Text>();
registerRequest = this.GetComponent<RegisterRequest>();
}
publicoverridevoid OnEnter()
{
base.OnEnter();
this.gameObject.SetActive(true);
transform.DOScale(1, 0.4f);
closeButton = transform.Find("Exit").GetComponent<Button>();
closeButton.onClick.AddListener(OnCloseButtonClick);
registerButton = transform.Find("RegeistButton").GetComponent<Button>();
registerButton.onClick.AddListener(OnRegisterClick);
}
publicvoidOnCloseButtonClick()
{
transform.DOScale(0, 0.4f);
uiMng.PopPanel();
}
publicvoid OnRegisterClick()
{
string msg = "";
if (string.IsNullOrEmpty(userInfo.text)){
msg += "使用者名稱不能為空";
}
if (string.IsNullOrEmpty(passwordInfo.text))
{
msg += "/n密碼不能為空";
}
if(passwordInfo.text != rePasswordInfo.text)
{
msg += "密碼和重複密碼不一致";
}
if (msg !=null)
{
uiMng.ShowMessage(msg);
}
//進行註冊請求傳送資訊到伺服器端
registerRequest.SendRequest(userInfo.text, passwordInfo.text);
}
publicoverridevoid OnExit()
{
base.OnExit();
this.gameObject.SetActive(false);
}
}
9. 使伺服器對於客戶端的註冊請求做響應
這裡的話,當我們想要對客戶端的註冊做出響應,這裡牽扯到的Controller和Dao就是UserController和UserDao了,一個負責呼叫Dao裡的方法進行檢驗和註冊,一個來書寫註冊方法
UserController
publicstring Regist(string data, Clientclient, Sever server)
{
string[] str = data.Split(',');
string username = str[0];
string password = str[1];
bool res = userDao.GetUserByUsername(client.MysqlConn,username);
if (res)
{
Console.WriteLine("使用者名稱已經存在");
return ((int)(ReturnCode.Fail)).ToString();
}
userDao.RegisterUser(client.MysqlConn, username, password);
return ((int)(ReturnCode.Success)).ToString();
}
publicvoid RegisterUser(MySqlConnectionconn, stringusername, stringpassword)
{
//插值作為連線查詢
try
{
MySqlCommand cmd = new MySqlCommand("insert into user where [email protected] [email protected]", conn);
cmd.Parameters.AddWithValue("username",username);
cmd.Parameters.AddWithValue("password",password);
//執行插入語句
cmd.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine("插入使用者資訊出現異常" + e);
}
}
10.建立聲音的模組AudioMannager
我們將在AudioMannager裡來管理所有我們的聲音,這裡的話,我們先持有所有的聲音並且為我們的遊戲新增背景音樂
publicclassAudioManager : BaseManager {
//重寫基類對於facade的呼叫
publicAudioManager(GameFacade facade) : base(facade) { }
//接下來的話,我們將在這個類裡面,持有所有的聲音
privateconststring sound_prepath = "Sounds/";
privateconststring sound_alert = "Alert";
privateconststring sound_Bgfast = "Bg(fast)";
privateconststring sound_Bgmoderate = "Bg(moderate)";
privateconststring sound_ButtonClick ="ButtonClick";
privateconststring sound_Miss="Miss";
privateconststring sound_ShootPerson ="ShootPerson";
privateconststring sound_Timer = "Timer";
privateAudioSource bgAudioSource;
privateAudioSource normalAudioSource;
publicoverridevoid OnInit()
{
//在這裡我們初始化我們的音樂
base.OnInit();
GameObject audioPlay = new GameObject("AudioSource(GameObject)");
bgAudioSource= audioPlay.AddComponent<AudioSource>();
normalAudioSource = audioPlay.AddComponent<AudioSource>();
bgAudioSource.clip = LoadClip(sound_prepath, sound_Bgfast);
PlaySound(bgAudioSource, true);
}
publicAudioClip LoadClip(stringprepath,stringsoundName)
{
//指定型別後在AudioClip中進行載入
returnResources.Load<AudioClip>(prepath + soundName);
}
publicvoidPlaySound(AudioSource audioSource,bool isLoop=false)
{
audioSource.Play();
audioSource.volume = 0.2f;
audioSource.loop = isLoop;
}
}
11.新增按鍵聲音
這裡的話,我們通過GameFacade實現對AudioMannager的呼叫,然後的話我們處理點選事件都是在UiPanel下呼叫的,這樣的話,我們通過GameFacde中介,然後給BasePanel寫一個點選播放音樂的方法,這樣,各個子panel就都能播放聲音了
1.提供兩個方法,分別播放迴圈的音樂和不迴圈的音樂
publicvoid PlayBgSound(string soundName)
{
bgAudioSource.clip= LoadClip(sound_prepath, soundName);
PlaySound(bgAudioSource, true,0.2f);
}
publicvoid PlayNormalSound(string soundName)
{
normalAudioSource.clip = LoadClip(sound_prepath, soundName);
PlaySound(normalAudioSource, false,1f);
}
2. 在GameFacade裡呼叫
publicvoid PlayBgsound(string soundName)
{
audioManager.PlayBgSound(soundName);
}
publicvoid PlayNormalSound(string soundName)
{
audioManager.PlayNormalSound(soundName);
}
3.在建立UIPanel的時候注入GameFacade的值
instPanel.GetComponent<BasePanel>().Facade= facade;
3. 在BasePanel裡建立播放點選聲音的方法,設定GameFacade的值
protectedGameFacade facade;
public GameFacade Facade
{
set
{
this.facade = value;
}
}
4. 接下來在各個面板呼叫即可
12.讓我們點選註冊時候直接彈出面板
這一塊的話我們做的和之前彈出其他面板思路是一樣,將RoomList做成Prefab,然後修改我們的panelType新增上roomlist,當然同時我們也要修改一下我們的的json檔案裡的路徑保證我們的PanelType能被讀取到
當然這裡有個問題,我們的登入是非同步處理,而不是在主執行緒中處理,這裡的話和之前的ShowMessage是一樣的,那我們解決辦法也和那個類似,我們更改PanelType也放在非同步中進行處理即可
1.我們給UiManager的父類BaseManager裡實現一個方法Update
2.這個互動我們放在GameFacade裡去實現,因為我們這個更改Panel要在主執行緒中進行
publicvoid UpdateMannager()
{
audioManager.Update();
cameraManager.Update();
requestManager.Update();
playerManager.Update();
clientManager.Update();
uIManager.Update();
}
3. 這樣我們就能在Uimanager裡重寫我們的Update方法來執行了,在裡面修改我們的PanelType,呼叫這個非同步方法
publicoverridevoid Update()
{
base.Update();
if(panelTypeToPush != UIPanelType.None)
{
PushPanel(panelTypeToPush);
panelTypeToPush = UIPanelType.None;
}
}
publicvoidPushPanelSync(UIPanelType panelType)
{
panelTypeToPush = panelType;
}
4.這樣的話,我們就可以在LoginPanel裡的OnResPonse,對是否登入成功,做出響應了
publicvoidOnResponse(ReturnCode returnCode )
{
if(returnCode == ReturnCode.Success)
{
//判斷登入成功,進入房間列表
uiMng.PushPanelSync(UIPanelType.RoomList);
}
else
{
uiMng.ShowMessageSync("登入失敗,使用者名稱或者密碼不正確");
}
}
13.伺服器查詢使用者資訊返回給客戶端
這裡的話我們仿照一下UserDao的寫法就行,實際就是對result表通過userid進行查詢,再讓伺服器在登入成功時轉發給客戶端
1. 建立Model層的Result類
classResult
{
//這裡我們設定返回Result的幾個屬性
public Result(int id, int userid, int totalcount,int wincount)
{
this.Id = id;