Unity3d学习之路-初识GameSparks多人游戏插件
初識GameSparks多人游戲插件
- 初識GameSparks多人游戲插件
- 簡介
- GameSparks介紹
- 創建游戲
- 云服務配置
- Authentication
- Matches
- Challenge
- MatchFoundMessage
- ChallengeIssuedMessage
- Event
- 游戲邏輯云代碼
- Unity實現
- 登錄/注冊界面
- 主菜單界面
- 游戲界面
- 小結
- 初識GameSparks多人游戲插件
簡介
本文跟隨Building a Turn-Based Multiplayer Game with GameSparks and Unity對GameSparks進行學習,做一個聯網五子棋的游戲,教程中在GameSparks中的js云代碼我沒有修改,Unity上代碼有所修改
GameSparks介紹
GameSparks是一個云服務,可以提供用戶認證,自定義匹配,回合制或多人游戲,玩家和游戲數據存儲等功能。GameSparks可以與客戶端通信,可以在通信攔截點(比如收到請求但在處理前,發送消息前等)執行云代碼,完成自定義的功能。在unity中使用GameSparks,直接在官網下載,然后導入項目中即可
創建游戲
官方教程創建一個游戲比較友好,講得很清楚,這次作業做一個聯網五子棋游戲,所以就只使用GameSparks提供的Events、Multiplayer、Cloud Code服務。
云服務配置
Authentication
用戶驗證不用修改,直接使用GameSparks提供的驗證方法,使用用戶名和密碼登錄。
Matches
Matches類似于一個游戲房間,它可以設置游戲開始的最低人數和最多人數,在Thresholds中可以自定義玩家匹配,還可以使用自定義的腳本對匹配過程進行控制等。通過Configuration/Multiplayer可以進入該頁面。
Challenge
Challenge類似于一個游戲,當Matches匹配成功達到進入游戲的最低人數的時候,就會觸發Challenge開始。在整個游戲中,客戶端傳遞的消息,會在Challenge的有關函數中進行處理,通過Configuration/Multiplayer可以進入該頁面。
MatchFoundMessage
在 Configurator/CloudCode選擇UserMessages/MatchFoundMessage,編寫在匹配成功時候將要執行的代碼。這次游戲是判斷客戶端的玩家ID是不是第一個先匹配的玩家ID,然后將它作為挑戰者(類似于房主),然后創建一個新的Challenge,將其他玩家添加到Challenge中。代碼見:博客傳送門
ChallengeIssuedMessage
在 Configurator/CloudCode選擇UserMessages/ChallengeIssuedMessage,編寫Challenge創建成功后將Challenge的詳細信息發送到客戶端。代碼見:博客傳送門
Event
Event是客戶端進行某個操作后會觸發云端的實現,可以定義傳入的參數。這次游戲創建一個Move事件,當客戶端的玩家落子的時候會觸發這個事件。參數是X,Y,代表落子的位置。
游戲邏輯云代碼
Board
在Configurator/CloudCode選擇Modules,新建一個Modules叫Board。使用一個一維數組存儲了棋盤上的信息(棋子類型或空),可以初始化棋盤,得到棋盤的對應位置的信息,修改棋盤對應位置的信息,檢測落子是否有效以及檢測游戲是否結束。代碼見:博客傳送門
Move
在Configurator/CloudCode選擇ChallengeEvents/Move,在里面實現當玩家落子之后的代碼,玩家落子后,傳入發送落子位置的消息到服務器上,然后服務器獲取玩家的信息使用剛才新建的Board,判斷落子是否有效,切換下一個玩家,檢測游戲是否結束。代碼見:博客傳送門
Unity實現
登錄/注冊界面
首先導入GameSparks的unity包,然后找到GameSparksSettings將網頁上的Api Key和API Secret填入。
創建一個空對象,命名為GameSparksManager,然后將GameSparksUnity腳本作為組件,Settings選擇GameSparksSettings。
創建UI,在Panel上有用戶名輸入框,密碼輸入框,注冊按鈕,登錄按鈕。將LoginPanel腳本掛載在Panel上。腳本使用GameSparks的Api,在按鈕點擊的時候向服務端發送消息,GameSparks會自動檢測用戶是否重名,密碼是否正確等,并且將消息返回客戶端。在登錄、注冊成功成功則轉到主界面。
public class LoginPanel : MonoBehaviour {public InputField userNameInput; //用戶名輸入框public InputField passwordInput; //密碼輸入框public Button loginButton; //登錄按鈕public Button registerButton; //注冊按鈕public Text errorMessageText; //錯誤消息文本void Awake(){loginButton.onClick.AddListener(Login);registerButton.onClick.AddListener(Register);}private void Login(){BlockInput();//發送登錄用戶的請求AuthenticationRequest request = new AuthenticationRequest();request.SetUserName(userNameInput.text);request.SetPassword(passwordInput.text);request.Send(OnLoginSuccess, OnLoginError);}private void OnLoginSuccess(AuthenticationResponse response){//切換到游戲開始界面LoadingManager.Instance.LoadNextScene();}private void OnLoginError(AuthenticationResponse response){UnblockInput();//將錯誤信息顯示出來errorMessageText.text = response.Errors.JSON.ToString();}private void Register(){BlockInput();//發送注冊用戶的請求RegistrationRequest request = new RegistrationRequest();request.SetUserName(userNameInput.text);request.SetDisplayName(userNameInput.text);request.SetPassword(passwordInput.text);request.Send(OnRegistrationSuccess, OnRegistrationError);}private void OnRegistrationSuccess(RegistrationResponse response){//注冊成功則登錄Login();}private void OnRegistrationError(RegistrationResponse response){UnblockInput();errorMessageText.text = response.Errors.JSON.ToString();}//禁用輸入private void BlockInput(){userNameInput.interactable = false;passwordInput.interactable = false;loginButton.interactable = false;registerButton.interactable = false;}//可以使用輸入private void UnblockInput(){userNameInput.interactable = true;passwordInput.interactable = true;loginButton.interactable = true;registerButton.interactable = true;} }加載場景函數,通過場景管理得到當前場景的索引,可以知道前或后一個場景的索引。將當前場景命名為Login,創建新的場景分別命名為MainMenu和Game。并且按順序加入Bulid場景中.將LoadingManager作為組件掛載在GameSparksManager上。
public class LoadingManager : Singleton<LoadingManager> {public void LoadNextScene(){//得到當前場景的索引int activeSceneIndex = SceneManager.GetActiveScene().buildIndex;SceneManager.LoadScene(activeSceneIndex + 1);}public void LoadPreviousScene(){int activeSceneIndex = SceneManager.GetActiveScene().buildIndex;SceneManager.LoadScene(activeSceneIndex - 1);} }主菜單界面
在Panel上創建一個play按鈕,當點擊的時候,向服務器發送請求匹配玩家的消息,然后等待匹配,匹配成功之后將跳轉到游戲場景。將MainMenuPanel腳本掛載到該Panel上,將play按鈕放到playButton處。
public class MainMenuPanel : MonoBehaviour {public Button playButton;void Awake(){playButton.onClick.AddListener(Play);MatchNotFoundMessage.Listener += OnMatchNotFound;//監聽挑戰開始事件ChallengeStartedMessage.Listener += OnChallengeStarted;}//建立挑戰成功,跳轉到游戲界面private void OnChallengeStarted(ChallengeStartedMessage message){LoadingManager.Instance.LoadNextScene();}private void Play(){BlockInput();//發送匹配玩家的請求MatchmakingRequest request = new MatchmakingRequest();request.SetMatchShortCode("DefaultMatch");request.SetSkill(0);request.Send(OnMatchmakingSuccess, OnMatchmakingError);}private void OnMatchmakingSuccess(MatchmakingResponse response) { }private void OnMatchmakingError(MatchmakingResponse response){UnblockInput();}private void OnMatchNotFound(MatchNotFoundMessage message){UnblockInput();}//保證只匹配一次private void BlockInput(){playButton.interactable = false;}private void UnblockInput(){playButton.interactable = true;} }創建一個ChallengeManager腳本,用于管理該游戲中玩家的信息以及挑戰的狀態改變。ChallengeStartedMessage在挑戰創建的時候觸發,ChallengeTurnTakenMessage在回合改變的時候觸發,ChallengeWonMessage在玩家獲勝后觸發,ChallengeLostMessage在玩家失敗后觸發。在挑戰創建的時候獲取兩邊玩家的ID以及用戶名,挑戰ID,拿到存儲在云端的棋盤信息。在回合改變的時候,切換當前玩家的用戶名,拿到最新的棋盤信息。將腳本掛載在一個空對象上。
public class ChallengeManager : Singleton<ChallengeManager> {public UnityEvent ChallengeStarted; //可注冊的事件,當挑戰開始public UnityEvent ChallengeTurnTaken; //可注冊的事件,切換回合public UnityEvent ChallengeWon; //可注冊的事件,勝利public UnityEvent ChallengeLost; //可注冊的事件,失敗private string challengeID; //挑戰的ID,游戲IDpublic bool IsChallengeStart; //挑戰開始public string CurrentPlayerName; //當前玩家名字public string HeartsPlayerName; //心形棋子的玩家名字public string HeartsPlayerId; //心形棋子的玩家IDpublic string SkullsPlayerName; //骷髏棋子的玩家名字public string SkullsPlayerId; //骷髏棋子的玩家IDpublic PieceType[] Fields; //整個棋盤的數據void Start(){//注冊監聽方法ChallengeStartedMessage.Listener += OnChallengeStarted;ChallengeTurnTakenMessage.Listener += OnChallengeTurnTaken;ChallengeWonMessage.Listener += OnChallengeWon;ChallengeLostMessage.Listener += OnChallengeLost;} private void OnChallengeStarted(ChallengeStartedMessage message){challengeID = message.Challenge.ChallengeId;HeartsPlayerName = message.Challenge.Challenger.Name;HeartsPlayerId = message.Challenge.Challenger.Id;SkullsPlayerName = message.Challenge.Challenged.First().Name;SkullsPlayerId = message.Challenge.Challenged.First().Id;CurrentPlayerName = message.Challenge.NextPlayer == HeartsPlayerId ? HeartsPlayerName : SkullsPlayerName;IsChallengeStart = true;//將數據庫中的棋盤數據拿到Fields = message.Challenge.ScriptData.GetIntList("fields").Cast<PieceType>().ToArray();ChallengeStarted.Invoke();}private void OnChallengeTurnTaken(ChallengeTurnTakenMessage message){//切換當前玩家名字CurrentPlayerName = message.Challenge.NextPlayer == HeartsPlayerId ? HeartsPlayerName : SkullsPlayerName;//將數據庫中的棋盤數據拿到Fields = message.Challenge.ScriptData.GetIntList("fields").Cast<PieceType>().ToArray();ChallengeTurnTaken.Invoke();}private void OnChallengeWon(ChallengeWonMessage message){IsChallengeStart = false;ChallengeWon.Invoke();}private void OnChallengeLost(ChallengeLostMessage message){IsChallengeStart = false;ChallengeLost.Invoke();}public void Move(int x, int y){//發送落子的位置信息LogChallengeEventRequest request = new LogChallengeEventRequest();request.SetChallengeInstanceId(challengeID);request.SetEventKey("Move");request.SetEventAttribute("X", x);request.SetEventAttribute("Y", y);request.Send(OnMoveSuccess, OnMoveError);}private void OnMoveSuccess(LogChallengeEventResponse response){print(response.JSONString);}private void OnMoveError(LogChallengeEventResponse response){print(response.Errors.JSON.ToString());} }游戲界面
棋子中每一個格子作為獨立的預制體,創建一個空物體命名為Field,添加Animator和Box Collider 2D組件,添加一個空對象作為其子對象,子對象添加Sprite Renderer組件,選擇所需的Sprite。創建新的Animator Controller,通過改變子對象的Sprite Renderer中的Sprite從而實現鼠標觸碰時候的加粗效果以及點擊之后的棋子落上去的效果。詳情見:博客地址。將Field腳本掛載到Field上,保存為預制體。
public class Field : MonoBehaviour {private Animator animator; //棋子的動畫,棋子的切換是由狀態機控制的private int x;private int y;void Awake(){animator = GetComponent<Animator>();//監聽回合切換事件ChallengeManager.Instance.ChallengeTurnTaken.AddListener(OnChallengeTurnTaken);}public void Initialize(int x, int y){this.x = x;this.y = y;}void OnMouseDown(){//發送落子位置ChallengeManager.Instance.Move(x, y);}void OnMouseEnter(){animator.SetBool("IsHovered", true);}void OnMouseExit(){animator.SetBool("IsHovered", false);}private void OnChallengeTurnTaken(){//玩家落子后,獲取落子位置的類型PieceType pieceType = ChallengeManager.Instance.Fields[x + y * ChessBoard.boardSize];//改變圖形if (pieceType == PieceType.Heart){animator.SetBool("IsHeart", true);}else if (pieceType == PieceType.Skull){animator.SetBool("IsSkull", true);}} }創建一個Board的空物體,用于加載棋盤,棋盤由一個15X15個格子組成,在初始化的時候算出他們的位置放在場景中。這里將預制體Field拖到fieldPrefab位置上。將ChessBoard腳本作為Board的組件
public class ChessBoard : MonoBehaviour {public const int boardSize = 15; //棋盤的大小public Field fieldPrefab; //棋盤的棋子預制體public float fieldSpacing = 0.25f; //棋盤棋子之間的間隔void Awake(){for (int x = 0; x < boardSize; x++){for (int y = 0; y < boardSize; y++){//算出每個棋子的位置float offset = -fieldSpacing * (boardSize - 1) / 2.0f;Vector3 position = new Vector3(x * fieldSpacing + offset, y * fieldSpacing + offset, 0.0f);Field field = Instantiate(fieldPrefab, position, Quaternion.identity, this.transform);field.Initialize(x, y);}}} }制作一個UI用于顯示當前是哪個玩家的回合,以及變換回合后對應玩家的棋子將會放大的效果。
public class HeadPanel : MonoBehaviour {public PieceType PlayerType; //玩家類型public Text text;public Image HeadImage; //玩家代表的棋子圖片private string PlayerName; //玩家名字void Awake(){//根據棋子類型獲取用戶名PlayerName = (PlayerType == PieceType.Heart) ? ChallengeManager.Instance.HeartsPlayerName : ChallengeManager.Instance.SkullsPlayerName;}void Update(){//顯示是哪一個用戶的回合if(PlayerName == ChallengeManager.Instance.CurrentPlayerName){HeadImage.rectTransform.localScale = new Vector3(1, 1, 1);text.text = PlayerName + " Turn";}else{HeadImage.rectTransform.localScale = new Vector3(0.7f, 0.7f, 0.7f);}} }制作一個UI,顯示失敗或者成功的界面,并添加返回按鈕
public class WinLossPanel : MonoBehaviour {public RectTransform content;public Text winText;public Text lossText;public Button backButton; //返回按鈕void Awake(){ChallengeManager.Instance.ChallengeWon.AddListener(OnChallengeWon);ChallengeManager.Instance.ChallengeLost.AddListener(OnChallengeLost);backButton.onClick.AddListener(OnBackButtonClick);//隱藏結束界面Hide();}private void Show(){content.gameObject.SetActive(true);}private void Hide(){content.gameObject.SetActive(false);}private void OnChallengeWon(){winText.enabled = true;lossText.enabled = false;Show();}private void OnChallengeLost(){winText.enabled = false;lossText.enabled = true;Show();}private void OnBackButtonClick(){//返回上一個場景LoadingManager.Instance.LoadPreviousScene();} }小結
此次游戲制作遇到一個問題在MainMenuPanel.cs使用
ChallengeManager.Instance.ChallengeStarted.AddListener(OnChallengeStarted);
注冊OnChallengeStarted函數沒有用,在挑戰開始的時候也不會觸發,但是在Field.cs中使用
ChallengeManager.Instance.ChallengeTurnTaken.AddListener(OnChallengeTurnTaken);
在回合切換的時候OnChallengeTurnTaken會被調用。所以最后只能使用
ChallengeStartedMessage.Listener += OnChallengeStarted;
注冊函數。但是不同腳本在不同場景的ChallengeStartedMessage好像是不一樣的,因為在單例類ChallengeManager中使用ChallengeStartedMessage.Listener 注冊函數可以觸發,但是在場景切換后MainMenuPanel使用上述方法注冊OnChallengeStarted方法并不會被觸發,所以我讓它在場景切換后不注銷之前的監聽解決這個問題
游戲視頻
總結
以上是生活随笔為你收集整理的Unity3d学习之路-初识GameSparks多人游戏插件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql中下杠怎么打_怎么打字母下方的
- 下一篇: 共轭梯度法Matlab的实现