用unity3d networking做unity3d 网络游戏架构可以吗

unity3d里 如何实现局域网联机_百度知道
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。
unity3d里 如何实现局域网联机
如题。我做了一个第一人称射击游戏,现在需要实现局域网(或者互联网)联机,Js代码应该写什么?给些例子、提示都行。
我看了一个photon server/cloud做服务器的例子,但是它自带的usephonton代码有些复杂,看了几天不太懂。不知道是不是把usephonton代码套...
我有更好的答案
com/Documentation/Components/class-NetworkView.html" target="_blank">http.unity3d如果只是做简单的联机.html另外unity支持自定义的socket通信,没必要要photonunity本身就提供了联机的组件networkview,所以,如果要做的话:///Documentation/Components/class-NetworkView,详见官方的文档<a href="http://docs
采纳率:44%
为您推荐:
其他类似问题
您可能关注的内容
unity3d的相关知识
&#xe675;换一换
回答问题,赢新手礼包&#xe6b9;SynMove.cs
using UnityE
using System.C
using UnityEngine.N
public class SynMove : NetworkBehaviour {
//当 SyncVar 发生改变时,UNet 会从 Server 端向所有有效的 Client 端发送这些改变。注意这里的方向,是从 Server 到 Client ,而不是从 Client 到 Server 的方向。
private Vector3 synP
[SerializeField]Transform myT
[SerializeField]float lerpRate = <span style="color: #;
// Update is called once per frame
void FixedUpdate () {
TransmitPosition ();
LerpPosition ();
void LerpPosition(){
//不是本地玩家
if (!isLocalPlayer) {
//非本地玩家的位置进行更新
myTransform.position = Vector3.Lerp (myTransform.position,synPos,Time.deltaTime*lerpRate);
//命令, 在客户端调用,但是在服务端运行,这是方法必须以 Cmd 开头
void CmdProvidePositionToServer(Vector3 pos){
//在服务器上为synPos 同步变量赋值
[ClientCallback]
//只在客户端调用
void TransmitPosition(){
//是本地玩家
if (isLocalPlayer) {
//就把本地玩家的位置传给服务器
CmdProvidePositionToServer (myTransform.position);
PlayerController.cs
using UnityE
using System.C
using UnityEngine.N
public class PlayerController : NetworkBehaviour {
// Update is called once per frame
void Update () {
//判断是不是本地玩家(自己只能控制自己的玩家,不能控制别人的玩家)
if (isLocalPlayer == false) {
//获取键盘横轴的值
float h = Input.GetAxis ("Horizontal");
//获取键盘纵轴的值
float v = Input.GetAxis ("Vertical");
//主角左右移动
transform.Translate (Vector3.right*h*<span style="color: #*Time.deltaTime);
//主角前后移动
transform.Translate (Vector3.forward*v*<span style="color: #*Time.deltaTime);
阅读(...) 评论()努力加载中,稍等...
暂无新消息
努力加载中,稍等...
已无更多消息...
这些人最近关注了你
努力加载中,稍等...
已无更多消息
努力加载中,稍等...
已无更多消息
& 在Unity中创建跨平台多人联网游戏(二)
Creating a Cross-Platform Multi-Player Game in Unity — Part 2
征集热心朋友翻译文章,奖励规则:每100汉字奖励10QB,30天内互动奖励0 - 50QB.
翻译请求:gad驿馆4月份翻译文章列表
该文章来自用户转载
Getting people to talk to each other can be hard work!Welcome back to the second part of this series on creating a cross-platform multi-player game in Unity. You’ll need to tackle the first part of this tutorial before you take on this one — otherwise this tutorial won’t make a whole lot of sense! :]Up to this point, you’ve spent a lot of time adding frameworks and filling out forms to get to the very first step of any multiplayer game — signing in and out. Now you can move on to the real work of making the game happen.In this tutorial part, you are going to learn the following:Some general game networking theoryHow to to connect Google Play ServicesHow to create a game app using Google Development console.The code necessary to have devices talk with each other.Mad driving skills! :]You can download the completed version for part one . You can read the first part .Note: If you are downloading the
for this part, you will need to configure the ClientID, or the project will not work. To learn how to obtain a ClientID, check out the first part of this tutorial series.An Introduction to MatchmakingMatchmaking is one of the main functions of Google Play game services and other multiplayer frameworks. When your app tells Google Play’s servers that the player is interested in a multiplayer game, Google Play then looks for other people in the world who are also interested in playing that same game at that same time.Matchmaking is a somewhat complex operation. the Google Play servers don’t just pick the n if one player is in Helsinki and the other player is in Sydney, the network connection between them would be terribly laggy. Unfortunately, even the best network programmers haven’t yet found a way to increase the speed of light. :]So Google Play may choose to wait for better options to come up for our Helsinki player and only match them against the player in Sydney if no better option comes along. Fortunately, you don’t have to worry abo you just need to know that the underlying service does a pretty good job at connecting people within a reasonable amount of time.Many multiplayer games, such as MMOs, require that all clients connect to a server in a traditional client-server model like the one illustrated below:However, many other games, like yours, will pair up players then connect their devices to each other through a peer-to-peer mesh network, where all players’ devices talk directly to each other as shown below: Invites, or Auto-Match?Traditionally, there are two ways that players are matched with opponents:Players can request to be matched up with specific friends.Players can ask to be auto-matched, which tells Google Play to find other people looking to play this game.I find that vast majority of games use auto- players tend to be impatient and just want to start a game instead of waiting around for their friend to see the invitation notification and decide whether or not to accept it.Invitations typically happen when two players are in the same physical location and spontaneously decide to play a game, or they’ve scheduled a gameplay session ahead of time. In most other cases, players tend to stick with auto-matching.Note: Turn-based games are an entirely different story, as they aren’t as time-sensitive and players happily invite their friends — since they don’t have to wait around for them! :]For these reasons, you’ll implement auto-matching in your game.Adding Auto MatchingOpen your Circuit Racer project in Unity,
then open up your MultiplayerController script from the Scripts folder.Create the following private instance variables in your class:private uint minimumOpponents = 1; private uint maximumOpponents = 1; private uint gameVariation = 0;minimumOpponents is the minimum number of opponents to match your player against. You’ll create a two-player game for now, so set this to 1.maximumOpponents is the maximum number of opponents to match your player against. Since it’s a two-player game, set this to 1 as well.gameVariation specifies which particular multiplayer variation of your game that you wish to play. If Circuit Racer had a racing mode and a destruction derby mode, you wouldn’t want the racing players to end up auto-matched with people playing in the destruction derby!The variables are all unsigned integers, meaning that they cannot hold a negative value. After all, you wouldn’t to play a game with negative players. Would that mean that you would cease to exist? :]You could specify a variation of 1 for racing players, and a variation of 2 for destruction derby players to keep them separated. Using too many variants of your game segments the players and means there’s fewer players in each pool. In this tutorial, you only have the one variant, so use 0 as the default.Next, add the following method to your MultiplayerController class: (MonoDevelop will complain that your argument is invalid because you’ve declared that MultiplayerController will be your RealTimeMultiplayerListener, and you haven’t set it up that way — but that’s an easy fix.)private void StartMatchMaking() { PlayGamesPlatform.Instance.RealTime.CreateQuickGame (minimumOpponents, maximumOpponents, gameVariation, this); }You will notice that you’ve passed in all your private instance values. The final argument is the RealTimeMultiplayerListener instance that receives any messages from the Google Play games service about the status of your multiplayer game. To keep all multiplayer logic contained in the same class, you use this to indicate that the MultiplayerController class is your listener.Now fix the compile error by changing the following line at the top of your class:public class MultiplayerController {…to the followingpublic class MultiplayerController : RealTimeMultiplayerListener {Here you declare that MultiplayerController conforms to the RealTimeMultiplayerListener interface, which is a list of methods a class p this is very similar to a protocol in Objective-C.Look at the SignInAndStartMPGame(); you’ll see there are two places in the code where the comments say you can start a multiplayer game like so:// We could start our game nowReplace those comments with the following method call:StartMatchMaking();Head back to Unity, and you’ll see…a bunch of new errors!Unity is complaining that you aren’t conforming to the interface as you promised. The RealTimeMultiplayerListener interface says any conforming class must implement the OnRoomConnected(bool success) method, among others.To get rid of the errors, you can create a few mostly stubbed-out methods for the time being.Note: Here’s a handy shortcut to generate stub methods in MonoDevelop: right-click on a blank line in your file and select Show Code Generation Window. Select Implement Interface Methods from the resulting dialog, check all the methods that appear, hit Return, and MonoDevelop automatically adds all the selected stub methods for you!You’ll replace MonoDevelop’s boilerplate exception handling code with something that just prints out simple debug messages for now.First, add the following utility method in MultiplayerController that prints out status messages from your matchmaking service:private void ShowMPStatus(string message) { Debug.Log(message); }Now you can tackle each interface method in turn.Add the following method, or alternately replace the contents of your stub method if you auto-generated it in the previous step:public void OnRoomSetupProgress (float percent) { ShowMPStatus ("We are " + percent + "% done with setup"); }OnRoomSetupProgress indicates the progress of setting up your room. Admittedly, it’ on iOS in particular, I think it jumps from 20% to 100%. But hey, it’s better than nothing! :]Some of you may be wondering what is a “room”? In Google Games terminology, a room is a virtual place that players gather to play real time games.Add the following method, or replace the contents of your stub method:public void OnRoomConnected (bool success) { if (success) { ShowMPStatus ("We are connected to the room! I would probably start our game now."); } else { ShowMPStatus ("Uh-oh. Encountered some error connecting to the room."); } }OnRoomConnected executes with success set to true when you’ve successfully connected to the room. This would normally be the point where you’d switch to a multiplayer game.Add or replace the following method:public void OnLeftRoom () { ShowMPStatus ("We have left the room. We should probably perform some clean-up tasks."); }OnLeftRoom tells you that your player has successfully exited a multiplayer room.Add or replace the following method:public void OnPeersConnected (string[] participantIds) { foreach (string participantID in participantIds) { ShowMPStatus ("Player " + participantID + " has joined."); } }You will receive an OnPeersConnected message whenever one or more players joins the room to which your local player is currently connected. You’ll learn more about participantIds later, but for now all you need to know is that they’re unique IDs for a specific player in this gameplay session.Add or replace the following method:public void OnPeersDisconnected (string[] participantIds) { foreach (string participantID in participantIds) { ShowMPStatus ("Player " + participantID + " has left."); } }OnPeersDisconnected is similar to OnPeersConnected but it signals that one or more players have left the room.Now for the last interface method! Add or replace the following method:public void OnRealTimeMessageReceived (bool isReliable, string senderId, byte[] data) { ShowMPStatus ("We have received some gameplay messages from participant ID:" + senderId); }You call OnRealTimeMessageReceived whenever your game client receives gameplay data from an this handles all of your multiplayer traffic.Once you’ve added all of the above methods, head back to Unity and check that all your compiler errors have resolved. If so, hit Command-B to export and run your project in Xcode. When the game starts, click the Multiplayer button and check the console log, where you should see something similar to the following:DEBUG: Entering internal callback for RealtimeManager#InternalRealTimeRoomCallbackDEBUG: Entering state: ConnectingStateWe are 20% done with setupThat means you’re in a two-player multiplayer lobby, waiting for another player to join. Success! :]However, you’re the only person on earth who can play this game, so you might be waiting a looong time for someone else to join. Time to fix that.Running on a Second DeviceIt’s quite difficult to test a Unity multiplayer game on a real device and the iOS simulator at the same time, so to make your life easy you’ll use two physical devices to test your multiplayer functions.It’s safe to assume that since you’re reading this
(where the iOS tutorials outnumber the Android ones by about 60 to 1), your second device also runs iOS. In that case, you can skip the section below. For those of you who want (or need) to run this on an Android device, read on!Running on AndroidIf you’ve never run an Android app before, I recommend you read through the excellent “” series by Matt Luedke, as this section only presents a brief summary of the steps required.Download and install Android Studio from . Click the Check for updates now text at the bottom of the welcome screen to ensure you have the latest version installed.Next, get the latest version of the SDK: at the welcome dialog, click on Configure\SDK manager. Make sure you have the latest version of:Android SDK ToolsAndroid SDK Platform-toolsAndroid SDK Build-toolsThe Android API which corresponds to the device you have. Not sure what device you have? Go to Settings\About phone and look for the Android Version number.Android Support LibraryGoogle Play ServicesIf the status of any of these items is Not installed or Update available, simply check the box next to the item and click Install xx packages… as shown below:Accept all the licenses as you’re prompted, and you’re good to go! Now that you’ve installed the requisite SDKs, you can quit Android Studio.Note: If you used Eclipse in the past and were left a little underwhelmed, give Android S it’s turning out to be quite a nice product.Make sure USB debugging is turned on for your device, as noted in Matt’s tutorial: If you have a device, you don’t need any silly provisioning profiles. You just need to turn on USB debugging for your device. Sometimes the checkbox option is available just by going to Settings & Developer Options on your device. Check these instructions for more details.Other times, you have to do some weird shenanigans. I can’t make this up! A direct quote from Android: “On Android 4.2 and newer, Developer options is hidden by default. To make it available, go to Settings & About phone and tap Build number seven times. Return to the previous screen to find Developer options.”Head back to Unity and hook up your device to your computer. Select Unity\Preferences then select External Tools from the dialog that appears. Ensure the Android SDK location is pointing to the right place: on my machine, it’s in /Applications/AndroidStudio/sdk/ but your setup might differ.Select Google Play Games\Android Setup…; in the dialog box that appears, you may already see the Application ID for your app. If it’s not there, re-enter it:If your Application ID isn’t in the dialog box, and you don’t remember what is, here’s how to find it again:Go back to the Play Developer console at
Click on Game Services (the little controller icon) on the leftClick on your gameLook at the name of your game at the top of the screen. Next to it should be an 11-or-12 digit number: Copy-and-paste this value and then continue with the tutorial!Click Setup and after a moment or two you should see a confirmation dialog that everything has been set up correctly. Next, select File\Build Settings\Android\Switch Platform to make this the default platform.Click Build and Run, and Unity will prompt you for a filename for your Android apk choose whatever you’d like, but CircuitRacerAndroid is a good suggestion. Unity then compiles the app, transfers it to your device, and launches it!If you followed the above steps, you should see Circuit Racer running on your Android device. Try out the single player version of the game!Open up a terminal window and type:adb logcat | grep UnityThis reports back any debug output from your device. You don’t even need to restart your app to view the logs!I/Unity
[Play Games Plugin DLL] 11/21/14 11:04:20 -08:00 DEBUG: Starting Auth Transition. Op: SIGN_IN status: ERROR_NOT_AUTHORIZEDI/Unity
( 5914):I/Unity
( 5914): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 49)I/Unity
( 5914):I/Unity
[Play Games Plugin DLL] 11/21/14 11:04:20 -08:00 DEBUG: Invoking user callback on game threadI/Unity
( 5914):Return to the main menu of the game and tap on the multiplayer option. You’ll likely be prompted to sign in, but the attempt will probably fail in a few moments:Don’t panic — you simply haven’t set up your Client ID for Android as you did for iOS in Part 1 of this tutorial.Go to the Play Developer console () and select the Game Services icon. Select your app, then click on Linked Apps. Click Link another app then select Android:Give this a name such as Circuit Racer Debug. Most developers create two Client IDs: one for debug releases, and one for production. Under Package Name, use the sample BundleID you’re using for the iOS version. Developers in the real world often like to add “.debug” to the end of this, but you’ll just keep things simple for the sake of this tutorial.Turn on Real-time multiplayer, just as you did for the iOS version. Your screen should look like this:Click Save, then Continue, and finally click Authorize your app now.Next you’re prompted for your package name, which should be filled out for you, and a signing certificate fingerprint. Hmm, that’ how do you get that?Execute the following command in Terminal:keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -vWhen prompted for your password, enter android, which admittedly is a terrible password, but good enough for the purposes of this tutorial. :]You’ll see the following output:Certificate fingerprints:
MD5: (Lots of hex numbers) SHA1: (Even more hex numbers) SHA256: (Yet more hex numbers) Signature algorithm name: SHA1withRSA
Version: 3Copy the hex string next to the SHA1 entry and paste it into the Signing certificate fingerprint field in the dialog box as shown:The above steps tell Google Play’s servers that “I am the owner of com..CircuitRacer, and here is a giant random number associated with me that basically guarantees it.” At this point, nobody else can claim to own a Client ID for com..CircuitRacer.Finalyl, click Create Client and you’re done!Build and note that in the Android world, you can select Replace when saving your apk, since there are no additional post-process steps as there are the Xcode world.This time around, you should be able to sign in to y make sure you’re using a different Google account than the one you’re signed into on your iOS device, and that has also been listed as a Tester account. You’ll soon join the multiplayer lobby for your game.Running on iOSHere’s how to get your game running on two iOS devices:Stop the app in XCode.Plug your second device in to your computer.Run the app again in Xcode on your second device. You don’t need to re-export from U just hit Run in Xcode.Once your game is running, sign in to your second iOS device with a different Google account than the one you are using on your first device, and one that has been listed as a Tester account under the Game Services section of the Play Developer console.Once you’ve signed in, star the debug messages will tell you that you’ve joined the multiplayer lobby for your game.Go back to your first device, start up your Circuit Racer app and request to join a multiplayer game.After a few moments, you’ll see that your two accounts are connected with some resultant console output like the following:DEBUG: New participants connected: p_CKq_go_Vq4CMchAB,p_CJXhuOmO0se-BRABDEBUG: Fully connected! Transitioning to active state.DEBUG: Entering state: ActiveStateDEBUG: Entering internal callback for RealTimeEventListenerHelper#InternalOnRoomStatusChangedCallback
We are connected to the room! I would probably start our game now.As far as the Google Play library is concerned, you’re in a real multiplayer game — you just can’t see your opponent’s car or actually interact with the game in any way. Boo. In fact, if you click on the Multiplayer button again, it won’t work because the service thinks you’re still in a game. You’ll need to kill both apps and restart them again to join another multiplayer game.The important thing is that the Google Play games service has matched up the two accounts on your devices and, in theory, you could start sending messages between the devices.Since there aren’t any magazines lying around the multiplayer waiting room of your game to read while you wait for another player to join, It might be nice to add a little UI to your game that shows the progress of connecting to the multiplayer game.Adding a Simple Waiting Room UIThe Google Play Games library you’ve incorporated into your Xcode build has some built-in support for a creating multiplayer waiting room UI. For some reason though, the Unity plug-in doesn’t take advantage of it. I suspect that most real game developers would prefer to build their own waiting room interface with their own themed graphics, which is the approach you’ll follow in this tutorial.Open MainMenuScript.cs and add the following public variable.public GUISkin guiSkin;This variable holds the image that will be the background of your dialog box. You will set this image using the Unity inspector.Now add the following two instance variables:private bool _showLobbyDialog; private string _lobbyMessage;These variables track whether you should display the lobby dialog box and what message it should contain.Now, add the following setter method to update _lobbyMessage:public void SetLobbyStatusMessage(string message) { _lobbyMessage = message; }Next, add the method below:public void HideLobby() { _lobbyMessage = ""; _showLobbyDialog = false; }The above method simply instructs the Main Menu to stop showing the lobby interface.Next, open MultiplayerController.cs and add the following public variable:public MainMenuScript mainMenuScript;The mainMenuScript variable holds a reference to your mainMenuScript. By doing so, you can call the public methods that you just added.Replace ShowMPStatus() with the following::private void ShowMPStatus(string message) { Debug.Log(message); if (mainMenuScript != null) { mainMenuScript.SetLobbyStatusMessage(message); } }With the changes above, instead of just printing debug messages to the console, you’re telling your mainMenuScript that it should be showing message as a lobby status message.Now you need to display these messages. Go back to MainMenuScript.cs and replace the following lines in OnGUI():} else if (i == 1) { RetainedUserPicksScript.Instance.multiplayerGame = true; MultiplayerController.Instance.SignInAndStartMPGame(); }…with the following code:} else if (i == 1) { RetainedUserPicksScript.Instance.multiplayerGame = true; _lobbyMessage = "Starting a multi-player game..."; _showLobbyDialog = true; MultiplayerController.Instance.mainMenuScript = this; MultiplayerController.Instance.SignInAndStartMPGame(); }Here you set _showLobbyDialog to true, which indicates you’re ready
you also tell MultiplayerController that this is the class to which it should send lobby status messages.Next, at the top of OnGUI() wrap that entire for loop inside another if block as follows:if (!_showLobbyDialog) { for (int i = 0; i & 2; i++) {
// Lots of GUI code goes here,,,
} }This hides the menu buttons [when the lobby dialog box appears. Even though the dialog box will cover the buttons, they’ll still remain clickable — which could lead to some unexpected behavior from your game! :]Add the following code to the end of OnGUI():if (_showLobbyDialog) { GUI.skin = guiSkin; GUI.Box( Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage); }This displays the dialog box on the screen.Head back to Unity and go to the MainMenu scene. Select MainMenuGameObject, click the little target next to Gui Skin, and then select GameGuiSkin from the Assets dialog:This sets the skin of your game using the public guiSkin variable you declared at the start of this section.Bui this time you should see your online connection status as a nice series of dialog boxes:That’s a little more comforting to a player who can’t view the Xcode console log! :]However, you’re still stuck with this dialog box that never goes away. As nice as the dialog is, playing an actual game would be a lot more fun.Adhering to the Delegate PatternSome of you may have noticed that the code above violates the principle of encapsulation. MultiplayerController directly communicates with MainMenuScript, which means the two classes are highly coupled. If you wrote some really awesome code in MultiplayerController and wanted to reuse it in your new exciting boat racing game “Circuit Sailor”, you’d be in a hard spot.In much the same way that you use the delegate pattern in Objective-C to logically separate one class from another, you can create an interface in C# to do the same thing.Head back to Unity and open the Scripts folder in your assets panel. Right-click the panel and select Create\C# Script. Name the script MPInterfaces, then open up the file for editing.Replace the entire file (yep, the whole thing) with the following code:public interface MPLobbyListener { void SetLobbyStatusMessage(string message); void HideLobby(); }Here you declare an interface, which as you’ll recall is sort of like a contract. Any class that implements this interface promises that it will add any requisite methods.Go back to MultiplayerController and modify the following variable:public MainMenuScript mainMenuScript;…to the following:public MPLobbyListener lobbyListener;You’re replacing mainMenuScript, which was tied directly to a MainMenuScript class, with an instance of lobbyListener instead. You’re saying you don’t care what kind of class this is, so long as it implements the MPLobbyListener interface.Modify ShowMPStatus so that mainMenuScript variable is replaced by a lobbyListener variable:private void ShowMPStatus(string message) { Debug.Log(message); if (lobbyListener != null) { lobbyListener.SetLobbyStatusMessage(message); } }The following code decouples the current object from the MainMenuScript since the code now works upon an interface as opposed to an instance.Now open MainMenuScript.cs and modify the class declaration at the beginning of the file as follows:public class MainMenuScript : MonoBehaviour, MPLobbyListener {This change indicates that the class implements the MPLobbyListener interface.Now find the following line in OnGUI():MultiplayerController.Instance.mainMenuScript = this;..and modify it as follows:MultiplayerController.Instance.lobbyListener = this;This last bit of code removes the last reference of the MainMenuScript and uses the new interface instead.Since MainMenuScript already implements the required methods, it’s fulfilled its contract with the MPLobbyListener interface.If you were to build and run your app at this point, it would still look and run the same, But you could feel confident developing it further knowing that you have some nicely encapsulated code under the hood! :]Starting the Multiplayer GameNow that you’ve cleaned up your code, you can add some multiplayer game logic to your project.Add the following code to the if (success) block of OnRoomConnected in MultiplayerController.cs:lobbyListener.HideLobby(); lobbyListener = null; Application.LoadLevel("MainGame");This hides the lobby dialog, clears out your mainMenuScript and then loads up your MainGame scene. If you were to run your app at this point, you’d see that you’re taken into the game as soon as you’re connected.Having only one car on the screen somewhat limits the multiplayer experience. Your next task is to add the opponent’s car.Browse out your assets you’ll see that there’s an existing Prefab for an OpponentCar object. Click on it, then click Add Component and add a new Script Component. Name it OpponentCarController, then click Create and Add.Finally, double-click on the newly created script to edit it. OpponentCarController will represent the other it doesn’t need to be very smart, since your human opponents will do all the driving.At the top of your class definition, add the following variable:public Sprite[] carSprites;This variable is going to hold the sprites of various car images.Next, add the following method into the script:public void SetCarNumber (int carNum) { GetComponent&SpriteRenderer&().sprite = carSprites[carNum-1]; }This lets you store your collection of car sprites in an array (yellow, blue, and red); you can then set the car sprite for a specific player simply by calling SetCarNumber on it.Go back to Unity, then click on the OpponentCar Prefab. Find the Car Sprites entry in the script component, and change its size to 3. Then select car_1 for element 0, car_2 for element 1, and car_3 for element 2 as shown below:Assigning Player ColorsAt this point, you’re left with an interesting problem: how do you determine the color to assign to each player? If it was simply the player’s choice, all players could choose the yellow car. Or one player could think her car is red and her opponent’s car is blue, while her opponent thinks the exact opposite. How can you get all game clients to agree on the state of the world?At first, you might think “Who cares?” But it quickly becomes evident that this sort of thing does matter in many cases. In a poker game, all clients need to decide who should deal first. In a real-time strategy game, you need to decide who should start at which base.In your racing game, you need to decide which lane each
therefore this problem applies to you. Other game tutorials on this site usually solve this problem by gener the highest number goes first, deals first, gets the yellow car and so on.Instead of this approach, you can take advantage of the participant ID entity in Google Play Games.Each player is assigned a participant ID by the game service when they join a game. This participant ID is different than a traditional player ID as it is randomly generated and only exists for the duration of that gameplay session. It’s a nice way for strangers to refer to each other in a game without having to use any data that could be traced back to the actual player.You can easily sort out the game configuration by sorting the list of participant IDs and assigning qualities to players based on thei the yellow car goes to the first listed participant, the blue car to the second, etc. In a poker game, the player listed first could be the dealer, and so on.Note: Google Play engineers have asked me to point out that these participant IDs are only mostly it’s possible for one player to get a lower-ordered participant ID more often than other players.If your game design confers a major gameplay advantage to the player listed first, you might want to sort the list of participant IDs, then randomly choose the player who gets to be dealer, gets the sniper rifle, or gets the inside lane. In your case, the yellow car isn’t all that special, so you don’t need to do anything beyond sorting the list.Open MultiplayerController.cs and add the following line to the top of the file:using System.Collections.Generic;This ensures that List is defined.Next, add the following method:public List&Participant& GetAllPlayers() { return PlayGamesPlatform.Instance.RealTime.GetConnectedParticipants (); }This simply gets a list of all participants in the room. It’s worth noting here that the library already sorts this list for you by participantId, so you don’t need to sort it yourself.Add the following method to get the ParticipantID of the local player:public string GetMyParticipantId() { return PlayGamesPlatform.Instance.RealTime.GetSelf().ParticipantId; }Open GameController.cs and add the following imports:using System.Collections.Generic; using GooglePlayGames.BasicApi.Multiplayer;Now add the following variables near the top of the class definition:public GameObject opponentPrefab;
private bool _multiplayerReady; private string _myParticipantId; private Vector2 _startingPoint =
Vector2(0.f, -1.752321f); private float _startingPointYOffset = 0.2f; private Dictionary&string, OpponentCarController& _opponentScripts;You’ll be using all these variables in the next method. Add the following code to the empty implementation of SetupMultiplayerGame():// 1 _myParticipantId = MultiplayerController.Instance.GetMyParticipantId(); // 2 List&Participant& allPlayers = MultiplayerController.Instance.GetAllPlayers(); _opponentScripts =
Dictionary&string, OpponentCarController&(allPlayers.Count - 1); for (int i =0; i & allPlayers.Count; i++) { string nextParticipantId = allPlayers[i].ParticipantId; Debug.Log("Setting up car for " + nextParticipantId); // 3 Vector3 carStartPoint =
Vector3(_startingPoint.x, _startingPoint.y + (i * _startingPointYOffset), 0); if (nextParticipantId == _myParticipantId) { // 4 myCar.GetComponent&CarController& ().SetCarChoice(i + 1, true); myCar.transform.position = carStartPoint; } else { // 5 GameObject opponentCar = (Instantiate(opponentPrefab, carStartPoint, Quaternion.identity) as GameObject); OpponentCarController opponentScript = opponentCar.GetComponent&OpponentCarController&(); opponentScript.SetCarNumber(i+1); // 6 _opponentScripts[nextParticipantId] = opponentScript; } } // 7 _lapsRemaining = 3; _timePlayed = 0; guiObject.SetLaps(_lapsRemaining); guiObject.SetTime(_timePlayed); _multiplayerReady = true;Taking each numbered comment in turn:This gets the local player’s participant ID and stores it as a local variable.This grabs the list of sorted participants from the multiplayer controller so you can iterate through them.This calculates the start point for each car based on its order in the list.If the participantID of the current player matches the local player, then instruct the CarController script to set the car number, which determines its color.Otherwise, instantiate a new opponent car from the Prefab object and set its car number and color as well.You’re storing this carController in a dictionary with the participant ID as the key. Since you’ll be getting a lot of messages in the form “Participant ID xxx said so-and-so”, storing your opponents in a dictionary is an easy way to refer to them later by their participant ID.Finally, you initialize a few other game constants and set _multiplayerReady to true, signalling that you’re ready to receive multiplayer message. Since there’s no guarantee all clients will start at precisely the same time, weird things may happen if you begin to receive game messages while you’re still setting up. The _multiplayerReady flag will protect against that, as you’ll see later.You might be thinking “Hey, isn’t it overkill to create a dictionary to store all of my OpponentCarControllers, when there’s really only one? Why not just have a simple opponentCar variable and be done with it?” Although you’re only testing the game with two players at the moment, you’ll want the model to be flexible enough to handle more than two players in the future, and the dictionary is a good way to prepare for that scenario.Go back to Unity and open the MainGame scene. Select GameManager and set the OpponentPrefab variable in the inspector to be your OpponentCar prefab object, then save the scene as shown below:Build and start a multiplayer game and you’ll see that one player has been assigned the yellow car and the other has been assigned the blue — and each player can drive themselves around the track.
在Unity中创建跨平台多人联网游戏(二)
版权所有,禁止匿名转载;禁止商业使用;禁止个人使用。
翻译:黄春水(hcs2024)
审校:侯士伟(侯士伟)欢迎回到用unity构建一个跨平台多人游戏系列教程的第二部分。在学习这个部分内容之前你应该先掌握这个系列教程的第一部分,否则接下来的内容对你可能意义不大:]到目前为止,你已经在添加框架和填写表单方面花费了大量的时间,开始了准备制作任何类型的多人游戏的第一步工作—登入和登出。。现在你可以开始制作真正的游戏了。在这个教程部分,你将会学到下列知识:l一些通用的游戏网络原理l怎样连接google play服务l怎样使用Google开发控制台制作游戏应用l设备之间通信的必要代码l疯狂的开车技巧:]你可以从这里下载第一部分的,你也可以点击阅读第一部分的内容。注意:如果你正在下载这一部分的,你需要先配置一下 ClientID,否则工程不能正常工作。想知道怎么获取ClientID,回顾一下这个系列的第一部分内容。 关于Matchmaking的介绍Matchmaking 是google play 游戏服务和多人游戏框架的主要内容之一。当你的应用通知google play 当前玩家需要开始一个多人游戏的时候,google play就会搜索全世界同一时间同样想要正式开始这个游戏的其他玩家。Matchmaking会进行一些复杂的运作,google play并不是随便挑找到一个玩家就选中他,如果一个玩家在赫尔辛基(芬兰首都)而另一个玩家在悉尼,那他们之间的网络延迟会非常高,不幸的是,即使是最好的网络工程师也无法找到一个能够提高光速的方法(网络通讯速度是光速传输)。所以google play通常会等待有更好的选择来匹配赫尔辛基玩家,如果一直匹配不到更好的结果才会选择匹配悉尼的玩家。幸运的是你不需要当心这里面的实现逻辑。你只需要知道这个服务会在合理的时间内处理好如何连接各个玩家。很多多人对战游戏比如MMOs类型,需要全部的客户端连接一台服务器,就像下面图片展示的传统客户端-服务器模型:
然而还有很多的游戏你的游戏这样会通过对玩家进行两两匹配从而通过p2p网络来连接他们的设备,他们的设备之间可以像下面的图片那样直接通讯: 邀请,还是自动匹配?通常来说,玩家匹配对手有两种方式:1.玩家可以邀请自己的特定朋友来参与对战。2.玩家也可以自动匹配,google play会寻找其他玩家来参与对战。我发现许多主流的游戏都是使用自动匹配,许多玩家在等待开始游戏的时候会变得焦躁不安,只想尽快的开始游戏而不是向朋友发出邀请信息并等待他们的朋友是否同意自己的游戏邀请。邀请游戏通常是两个玩家在同一个地方并且决定一起玩某一个游戏,或者是他们在玩游戏之前就已经约好了。否则大部分情况下,玩家还是需要自动匹配。注意:回合制游戏是完全不一样的情况,因为这种类型的游戏不是时效性很强的游戏,人们一般乐意邀请朋友然后等待他们。综上所述,你应该在你的游戏里面实现自动匹配功能。 加入自动匹配用unity打开你的Circuit Racer 工程,然后在Scripts文件夹里打开MultiplayerController文件,在这个类里面加入下面几个私有变量:12345private uint minimumOpponents = 1; private uint maximumOpponents = 1; private uint gameVariation = 0;1.minimumOpponents 是你需要匹配到对手的最小数量,如果你现在创建的是一个双人对战游戏,那么这个值就
设为1.2.maximumOpponents是你需要匹配到对手的最大数量,由于这是一个双人对战游戏,那么这个同样也设为1.3.gameVariation 表示你游戏里面希望玩的某个模式,如果Circuit Racer 有赛跑模式和破坏模式的话,你不会希望参加赛跑模式的玩家自动匹配到参加破坏模式的玩家。这些变量都是无符号整型,这表示他们都不会是负数。你肯定不会和负数的玩家一起玩游戏,否则的话这表示你可能是想退出游戏?为了区分游戏类型,你可能会指定变量1表示赛跑模式变量2表示破坏模式。使用过多的变量来区分游戏类型会让你的玩家被分流导致每个类型的玩家数量都很少。在这个教程里面,你只有一种类型,用0来表示默认的类型。接下来,把下面的方法加入MultiplayerController 类(Mono develop 会提示你编译错误,因为你声明了变量MultiplayerController是一个RealTimeMultiplayerListener,但你还没有按此设置-----这个问题很容易解决。)12345private void StartMatchMaking() { PlayGamesPlatform.Instance.RealTime.CreateQuickGame(minimumOpponents,
maximumOpponents, gameVariation, this); }你应该注意到你已经传递了全部的私有变量,最后一个参数是RealTimeMultiplayerListener实例,它会接收google play游戏服务有关你的游戏状态的消息。为了保证全部的多人游戏逻辑在同一个类里面,你用这个变量来声明MultiplayerController是你的监听者。接下来解决编译错误,按照下面的代码来修改你的类:1public class MultiplayerController {修改为1public class MultiplayerController : RealTimeMultiplayerListener {在这里你声明MultiplayerController继承RealTimeMultiplayerListener 接口。里面有一些方法需要继承类去实现,这个和object-c的协议非常像。看一下SignInAndStartMPGame(); 你会看到有两个地方的代码注释了你可以从这里开始一个多人游戏。注释内容就是下面的样子:1用下面这个方法替换注释的内容1StartMatchMaking();回头看一下unity,你会发现有控制台有许多新的报错Unity解释了你没有实现所继承接口全部的方法。任何继承RealTime MultiPlayerListener接口的必须实现OnRoomConnected(bool success) 方法。为了先解决这些错误,你可以暂时声明一下这些方法。注意:monodevelope有一个声明方法的快捷方式:在文件空白行右击然后选择show code generation window。在弹出界面选择implement interface methods ,选中所有的出现的结果,点击返回,monodevelope会自动创建你选中的全部方法。首先,在MultiplayerController 加入下面的代码用来打印matchmaking 服务的状态信息:12private void ShowMPStatus(string message) {
Debug.Log(message);}现在你可以依次处理每个接口函数。 增加下面的方法,如果你在前面已经使用了自动生成模板函数的话那就直接替换123public void OnRoomSetupProgress (float percent){ShowMPStatus ("We are " + percent + "% done with setup");}OnRoomSetupProgress表示的进度,显然这样做很简单粗暴,特别是在ios上面,它会从20%直接跳到100%。但是这也比什么都没有要好吧。你可以能会对”房间”这个词产生困惑,在谷歌游戏术语里面,一个”房间”是指玩家聚集在一起进行实时游戏的虚拟空间。增加下面的方法,如果你在前面已经使用了自动生成模板函数的话那就直接替换123456public void OnRoomConnected (bool success){
if (success) {
ShowMPStatus ("We are connected to the room! I would probably start our game now.");
ShowMPStatus ("Uh-oh. Encountered some error connecting to the room.");
}} 当你成功连接到房间之后,OnRoomConnected方法会被执行,且success参数为true。增加或者替换下面的方法:12public void OnLeftRoom (){
ShowMPStatus ("We have left the room. We should probably perform some clean-up tasks.");}OnLeftRoom 会通知你你的玩家成功退出多人游戏房间。增加或者替换下面的方法:1234public void OnPeersConnected (string[] participantIds){
foreach (string participantID in participantIds) {
ShowMPStatus ("Player " + participantID + " has joined.");
}}当有一个或者多个玩家加入了你当前所在的游戏房间后,你会收到OnPeersConnected 消息,稍后你会进一步了解participantIds ,现在你只需要知道每一个加入游戏的玩家都有各自唯一的ID。增加或者替换下面的方法:1234567public void OnPeersDisconnected (string[] participantIds){foreach (string participantID in participantIds) {
ShowMPStatus ("Player " + participantID + " has left.");}}OnPeersDisconnected和OnPeersConnected 类似,但它是用来标识某个玩家离开游戏房间。下面是最后一个接口函数,增加或者替换下面的方法:1234public void OnRealTimeMessageReceived (bool isReliable, string senderId, byte[] data){ShowMPStatus ("We have received some gameplay messages from participant ID:" + senderId);}你可以用OnRealTimeMessageReceived处理游戏客户端接收到的在同一个房间任何从其他玩家发过来的数据,这个函数是处理你和其他玩家的交互信息。如果你已经处理好了上面说的全部内容,回到unity查看是否已经解决全部的编译错误,如果解决好了,点击 Command—B来导出然后在Xcode里面运行。游戏开始后,点击多人游戏按钮然后查看控制台日志,你会看到和下面差不多的内容:DEBUG: Entering internal callback for RealtimeManager#InternalRealTimeRoomCallbackDEBUG: Entering state: ConnectingStateWe are 20% done with setup这个表示你正在一个双人游戏大厅,等待另一个玩家加入,成功!但是你是这个世界上唯一一个可以玩这个游戏的玩家,所以你只能一直在等待其他玩家加入这个游戏。时候解决这个问题了。 在第二台设备上面运行分别在真机和ios的模拟器上面测试多人游戏是一件非常麻烦的事情。为了让事情简单一点你应该准备两台测试机来测试你的游戏。如果你是在 (这个网站ios教程的数量是安卓教程的60倍)这个网站阅读这篇文章,然后你的第二部设备也是ios系统。那你可以跳过下面这一节的内容。如果你打算用(或只能用)安卓设备来运行我们的程序,那就接着读下去。 在安卓环境下运行如果你之前从来没有运行过一个安卓应用的话,我建议你通读一下Matt Luedke,的“制作你的第一个安卓应用”这篇很好的文章,因为这个部分只展示一些必要的步骤要点。从. 下载安装 AndroidStudio 在欢迎界面点击检查是否有更新按钮来确保你安装的是最新版本 接下来,获取最新版本的sdk:在欢迎界面,点击 Configure\SDK manager,确保你有最新版本的:lAndroidSDK ToolslAndroidSDK Platform-toolslAndroidSDK Build-tools。l安卓api需要和你机器的安卓版本一致,如果不知道你机器的安卓版本, 在手机的 设置/关于里面可以找到安卓的版本号。lAndroidSupport LibrarylGooglePlay Services 如果上面这些内容的状态显示未安装或者有更新,点击旁边的按钮然后点击安装 XX安装包,就像下面这张图这样。 在安装的过程中一直点击接受,现在你已经安装好需要的sdk,可以退出Android Studio。注意:如果你过去一直使用Eclipse并且已经对它感觉厌倦的话,你可以试试Android Studio,这是一个非常好的工具。就像Matt的教程里面说的,确保打开你设备的USB调试功能。如果你有一台设备的话,你不需要做一些愚蠢的配置,你只需要打开设备的USB调试。一般来说,你可以在设置/开发者选项中找到这个功能的开关。或者可以查看说明书获取更多的细节。有些时候你不得不做一些奇怪的事情,我无法容忍的一点是,在安卓4.2和更新的版本里,开发者选项是默认隐藏的。为了能够看到开发者选项,你需要通过设置/关于手机然后点击版本号七次,回到前一个界面,你就能看到开发者选项了。回到unity,连接你的设备和电脑。点击Unity/Preferences 然后选择Exteranl Tools,确保安卓sdk地址指向正确的位置,在我的机器上面是/Applications/AndroidStudio/sdk/,但是你的可能会有所不同。 选择Google Play Games\Android Setup…;在显示的对话框中,你应该能够看到应用的Application ID。如果没有就重新打开一次。 如果你的Application ID没有显示在对话框,你也忘记了Application ID,下面教你怎么重新找回。1.回到 的游戏开发控制台。2.点击左边的游戏服务(一个很小的控制图标)3.点击你的游戏4.在屏幕上面查看你游戏的名字,旁边还有11位或者12位的数字。5.复制粘贴这个数然后继续我们的教程。 点击安装,等一会之后你会看到全部正确安装完成的确认框。接下来选择 File\Build Settings\Android\Switch Platform,确保使用的是安卓平台。点击Build and Run 。unity会让你给安卓安装包取个名字,你可以选择你喜欢的任意名字,但是CircuitRacerAndroid 是个不错的选择。接着unity编译app,发送到你的设备上面然后安装。如果你按照上面一步步做的话,你会看到Circuit Racer在你的安卓机器上面运行,测试一下游戏的单人模式。打开终端窗口然后输入:1adb logcat | grep Unity这个可以返回你机器上的调试信息,你甚至不需要重启你的应用来查看日志!I/Unity
[Play Games Plugin DLL] 11/21/14 11:04:20 -08:00 DEBUG: Starting Auth Transition. Op: SIGN_IN status: ERROR_NOT_AUTHORIZEDI/Unity
( 5914):I/Unity
( 5914): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 49)I/Unity
( 5914):I/Unity
[Play Games Plugin DLL] 11/21/14 11:04:20 -08:00 DEBUG: Invoking user callback on game threadI/Unity
( 5914): 回到游戏的主菜单,然后点击多人模式。您可能会被提示登录,但尝试登录可能会在短时间内失败:别慌---你还没设置安卓的 Client ID ,就像我们在第一部分教程设置ios Client ID的那样。打开游戏开发控制 () ,选择游戏服务图标。选择你的应用,接着点击链接应用。点击链接其他应用然后选择安卓。选择一个名字比如Circuit Racer Debug。大部分开发者会创建两个Client ID:一个用来打测试包,一个用来打发布包。在包名下面使用和ios版本相同的BundleID 。现实中的开发者经常喜欢在结尾加“.debug”,为了简单一点,你可以和教程保持一致。打开实时多人模式。就像ios版本一样,你的屏幕应该这样显示: 点击保存,然后继续,最后点击许可授权你的应用。接下来需要你为包取个名字,你可以随意起。然后一个证书。嗯,这是一个新东西,你要怎么获取证书?在终端执行下面的命令:keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list –v 当要求你输入密码时,你可以输入android,毫无疑问这是一个糟糕的密码,但是对于这篇教程来说就无所谓了你会看到下面这样的输出:Certificate fingerprints:
(许多16进制数字)
SHA1: (更多的16进制数字)
SHA256: (超级多的16进制数字)
Signature algorithm name: SHA1withRSA
Version: 3 复制SHA1后面的16进制数字,把它粘帖在证书的输入框内,就像下图这样:上面的这些步骤是告诉Google Play服务“我是com..CircuitRacer的拥有者,并且这里有一个巨大的随机数来保证我确实拥有它”,这样其他人就不能声明他拥com..CircuitRacer. 的Client ID。最后,点击Create Client 然后就完成了。再次编译运行你的游戏,你会发现在安卓里,你保存apk的时候可以选择替换,但是xcode就不能这么做,因为xcode还有后续的处理。这次你可以登录你的多人游戏了。确保你使用了和ios设备登录帐号不同的google账号,并且同样是在测试账号列表里面的账户。很快你就会发现你加入了多人游戏大厅。 在IOS上面运行接下来教你怎么样在两台ios设备上面运行你的游戏:1.停止运行Xcode上面的应用。2.把你的第二台设备接入电脑。3.用Xcode在你的第二台设备上面重新运行游戏。你不需要重新从unity中导出,只需要在Xcode上面点击运行。4.当你的游戏开始运行后,在谷歌游戏服务区域的开发者控制台测试账号列表中选择和你在第一台设备登录的不同账号在第二台设备上面登录。5.登录成功后开启多人游戏,调试信息会告诉你你已经加入了游戏的多人游戏大厅。6.回到你的第一台设备,打开你的Circuit Racer应用然后请求加入多人游戏。 过一小会,你会你的两个账号已经连接,控制台会输出如下的信息:DEBUG: New participants connected: p_CKq_go_Vq4CMchAB,p_CJXhuOmO0se-BRABDEBUG: Fully connected! Transitioning to active state.DEBUG: Entering state: ActiveStateDEBUG: Entering internal callback for RealTimeEventListenerHelper#InternalOnRoomStatusChangedCallback We are connected to the room! I would probably start our game now. 到目前为止谷歌游戏库关心的是你正在一个多人游戏中,尽管你现在看不到对手的车辆或者在用任何方法游戏里面进行操作。实际上,如果你这个时候再去点击多人游戏按钮,它也不会响应了,因为谷歌游戏服务认为你现在任然在游戏中,你需要关掉两个应用,然后再次重启之后加入另一个多人游戏。最重要的是谷歌游戏服务已经匹配好了你两台机子上面的账号,理论上来说,你现在可以在两台设备之间发送信息。由于你的游戏等待大厅在寻找其他玩家加入是没有杂志可供阅读,,所以最好加一个小的UI来显示加入多人游戏的进度。 添加一个简单的等待大厅UI你添加到Xcode的谷歌游戏库已经有一些针对创建多人游戏等待大厅UI的内置支持,因为一些原因,unity插件并没有使用它,我猜是因为开发者更希望使用他们自己的主题图片来构建等待大厅的ui,下面我来教你怎么实现这个功能。打开MainMenuScript.cs 然后加入下面的公有变量:1public GUISkin guiS这个变量会保存你对话框的背景图,你可以开unity的inspector设置图片。现在加入下面两个实例变量:12private bool _showLobbyDprivate string _lobbyM 这两个变量分别表示你是否需要显示等待对话框和里面显示的内容。现在,加入下面的设置函数来更新_lobbyMessage:1234public void SetLobbyStatusMessage(string message) {_lobbyMessage =} 接下来,加入下面的方法:12345public void HideLobby(){
_lobbyMessage = "";_showLobbyDialog = false;} 上面的方法只是简单的告诉主菜单停止显示等待大厅的ui接下来,打开MultiplayerController.cs 然后加入下面的公有变量:1public MainMenuScript mainMenuSmainMenuScript变量会保存mainMenuScript的引用,这样的话你可以执行你刚刚添加的全局方法。把ShowMPStatus()替换为12345private void ShowMPStatus(string message) {
Debug.Log(message);
if (mainMenuScript != null) {
mainMenuScript.SetLobbyStatusMessage(message);
}} 替换之后,你会通知mainMenuScript显示等待大厅的状态信息而不只是在控制台输出调试信息。现在,你需要显示这些信息,回到MainMenuScript.cs 然后在OnGui():里面把123} else if (i == 1) {
RetainedUserPicksScript.Instance.multiplayerGame = true;
MultiplayerController.Instance.SignInAndStartMPGame();}替换为:123456} else if (i == 1) {
RetainedUserPicksScript.Instance.multiplayerGame = true;
_lobbyMessage = "Starting a multi-player game...";
_showLobbyDialog = true;
MultiplayerController.Instance.mainMenuScript = this;
MultiplayerController.Instance.SignInAndStartMPGame();} 在这里,你设置_showLbbyDialog为true,来表明你准备显示对话框,然后告诉MultiplayerController这个类是游戏等待大厅状态信息的接受者。接下来在OnGUI()最上面用另一个if块把整个for循环包含在内,如下所示:123456if (!_showLobbyDialog) {
for (int i = 0; i & 2; i++) {
}}这个可以隐藏菜单按钮【当游戏大厅的对话框出现,虽然他们会盖住菜单按钮,但是按钮仍然可以点击—这样会导致一些无法预测的情况出现】。最后在OnGui()的最后面加上如下代码:123if (_showLobbyDialog) {
GUI.skin = guiS
GUI.Box(new Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage);} 这个会在屏幕上显示对话框。回到unity的主菜单场景,选择MainMebuGameObject,点击Gui Skin旁边的小圆圈,然后在Assets对话框里面选择GameGuiSkin。这这样可以用本节开始处声明的guiSkin变量来设置你的游戏皮肤。编译运行你的游戏,这次你会在一系列漂亮的对话框里面看到你的在线连接状态。 这个对不能看到Xcode的控制台输出日志的人来说会感觉舒服一点。然后,如果这个对话框存在你就无法更进一步,无论这个对话框有多好看,也比不上在游戏里面真正玩一把来的有趣。 坚持使用委托模式你可能会注意到上面代码违反了封闭原则。MultiplayerController直接和MainMenuScript交流,这意味着这两个类高度合。如果你在MultiplayerController里面写了一些很棒的代码然后想要复用在你新的快艇竞速游戏“Circuit Sailor”里面的时候,你就会陷入困境。就像你在Object-C里面使用代理模式来分离类和类之间的逻辑一样,你可以在C#里面用接口来做同样的事情。回到unity然后打开Asset面板的脚本文件夹。右击面板然后选择 创建\C#脚本。脚本命名为MPInterfaces,然后打开文件编辑。把整个文件替换为下面的代码(是的,全部内容)1234public interface MPLobbyListener {
void SetLobbyStatusMessage(string message);
void HideLobby();}在这里你声明了一个接口,你可以想象它就像一个合同一样的东西。任何继承这个接口的类必须保证它会添加接口里面定义的所有方法。回到MultiplayerController 然后把下面的变量123public MainMenuScript mainMenuScript;改为public MPLobbyListener lobbyL你正在把之前直接声明为MainMenuScript类的mainMenuScript替换为一个lobbyListener的实例。你声明了你不在意这是一个什么类,只要它实现了MPLobbyListener 接口。修改ShowMPStatus ,把mainMenuScript 变量替换为lobbyListener 变量123456private void ShowMPStatus(string message) {
Debug.Log(message);
if (lobbyListener != null) {
lobbyListener.SetLobbyStatusMessage(message);
}下面的代码从MainMenuScript 解耦了当前的对象,因为代码现在依赖接口而不是依赖一个实例。现在打开 MainMenuScript.cs象下面这样修改文件开头的声明1public class MainMenuScript : MonoBehaviour, MPLobbyListener {此更改表示该类实现了MPLobbyListener接口现在在OnGUI()里面找到下面的代码:1MultiplayerController.Instance.mainMenuScript = this;修改为下面这样1MultiplayerController.Instance.lobbyListener = this;最后这一点代码移除了最后一点MainMenuScript 的最后一个引用然后使用新的接口替换它。由于MainMenuScript 已经实现了要求的全部方法,所以它完全符合MPLobbyListener 接口。如果你这个时候编译运行你的游戏,它看起来没什么区别,但是你在开发的时候可以感觉更加自信因为你知道在底层你用了封装的很好的代码。开始多人游戏现在你已经理清楚了你的代码,你可以在项目里面加入一些多人游戏逻辑了。在MultiplayerController.cs的OnRoomConnected的if(success)代码块加入下面的代码:123lobbyListener.HideLobby();lobbyListener = null;Application.LoadLevel("MainGame");这些代码会隐藏等待大厅,移除游戏菜单,然后加载你的主游戏场景,如果你这个时候运行你的游戏,你会看到你被连接上的时候就被拉进游戏。在屏幕上面只有一辆车确实限制了多人游戏的体验,你接下来的任务就是增加一辆对手的车。打开你的assets文件夹,你会看到一个OpponentCar的预制件,点击它,然后点击添加脚本,点击添加一个新的脚本,命名为OpponentCarController,然后点击创建和添加。最后双击刚刚创建的脚本开始编辑。OpponentCarController会被作为另一个对手,它不需要非常聪明,因为你的另一个人类对手会全程驾驶它。在类的开头定义,加入下面的变量:1public Sprite[] carS这个变量会保存一系列车的图片。接下来,在脚本里面加入下面的方法:123public void SetCarNumber (int carNum) {
GetComponent().sprite = carSprites[carNum-1];}这个可以在数组里面保存你的汽车图片(黄色,蓝色,红色);当你为不同玩家设置不同的汽车图片是你只需要调用SetCarNumber就可以了。 回到unity,然后点击OpponentCar预制件,在脚本里面找到carSprites,把它的长度改为3,然后设置第一个变量为car_1,第二个变量为car_2,第三个变量为car_3,就像下面这样: 给玩家的车上颜色这个时候你会碰到一个有趣的问题:如何决定给每个玩家分配什么颜色?如果只是简单地按照玩家自己的选择,那么全部玩家可能都选择黄色,或者一个玩家可能选择自己是红色的而对手是蓝色的,但是他的对手却想的于此相反。你要怎么样来确保所有的游戏客户端对状态达成一致?首先,你可能会想谁会在意这件事,但很快你就会反应过来,,在许多情况下,这件事情是很重要的,比如在纸牌游戏里面,所有的玩家需要选择一个先出牌的人,在实时策略类游戏里面,你需要决定谁应该在哪个出生点开始。在你的赛车游戏里面,你需要决定每个玩家应该从哪一条赛道开始出发。因此你需要解决这个问题。其他游戏经常使用通用的随机数来解决这个问题,谁的数字更大就可以先出发,先开始,或则可以选择黄色的车等等。 你可以利用谷歌游戏持有的游戏实例id来特换这个随机数方案。每个玩家通过谷歌游戏服务加入游戏的时候都会被赋予一个特别的id。这个特别的id不同于传统的玩家id,它是随机的并且只存在于游戏期间。这个一个非常棒的方法来让陌生玩家之间可以互相交流而不需要其他任何可能用来反追踪真实玩家信息的数据。你可以很容易的对你的游戏配置进行排序,首先将参与者的ID列表进行排序,然后通过玩家在排序列表的顺序来分配不同的特征,比如黄色的车给排序列表的第一个人,蓝色的给第二个人等待,在扑克游戏里面,第一个玩家可以可以成为发牌员或者别的。注意:谷歌工程师曾让我指出这些特别的id只是大部分随机,有可能对于某一个玩家他会相对于其他玩家更容易拿到一个低排序的特别id。如果你的游戏设计上,排在第一位的玩家会获得明显的游戏优势,你可以通过玩家的特别id排序后,然后随机选择一个玩家成为发牌员,获得狙击枪或者获得赛车内道。在你的这个例子里,黄色的车并不是特别的东西,所以你只需要排序一下列表就可以了。打开MultiplayerController.cs 然后在开头加上下面这行:1using System.Collections.G这个可以保证List被声明过。接下来,加个下面的方法:123public List GetAllPlayers() {
return PlayGamesPlatform.Instance.RealTime.GetConnectedParticipants ();}这个可以获取游戏房间里面的参与者列表,它已经通过participantId排好序了,所以你不需要自己再重新排序。加入下面的方法来获取本地玩家的participantId:123public string GetMyParticipantId() {
return PlayGamesPlatform.Instance.RealTime.GetSelf().ParticipantId;}打开GameController.cs然后加入下面的命名空间:1using System.Collections.Gusing GooglePlayGames.BasicApi.M在开头的顶定义部分加入下面的变量:123456public GameObject opponentPprivate bool _multiplayerRprivate string _myParticipantId;private Vector2 _startingPoint = new Vector2(0.f, -1.752321f);private float _startingPointYOffset = 0.2f;private Dictionary&string, opponentcarcontroller=""& _opponentS&/string,&你会在下面方法中用到以上的变量,把下面的代码加入空的SetupMultiplayerGame():实现中:1234567891011121314151617181920212223242526_myParticipantId = MultiplayerController.Instance.GetMyParticipantId();List allPlayers = MultiplayerController.Instance.GetAllPlayers();_opponentScripts = new Dictionary&string, opponentcarcontroller=""&(allPlayers.Count - 1); for (int i =0; i & allPlayers.C i++) {
string nextParticipantId = allPlayers[i].ParticipantId;
Debug.Log("Setting up car for " + nextParticipantId);
Vector3 carStartPoint = new Vector3(_startingPoint.x, _startingPoint.y + (i * _startingPointYOffset), 0);
if (nextParticipantId == _myParticipantId) {
myCar.GetComponent ().SetCarChoice(i + 1, true);
myCar.transform.position = carStartP
GameObject opponentCar = (Instantiate(opponentPrefab, carStartPoint, Quaternion.identity) as GameObject);
OpponentCarController opponentScript = opponentCar.GetComponent();
opponentScript.SetCarNumber(i+1);
_opponentScripts[nextParticipantId] = opponentS
}}_lapsRemaining = 3;_timePlayed = 0; guiObject.SetLaps(_lapsRemaining);guiObject.SetTime(_timePlayed); _multiplayerReady = true;&/string,&下面是按照编号顺序的注释:1.这个获取本地玩家的participantId然后保存在局部变量里。2.这个从 multiplayerController里面获取排序后的玩家列表,你可以遍历列表。3.这个按照排序列表的顺序来计算每辆车的出发点。4.如果当前玩家的ParicipantID和本地玩家匹配,就通知CarController脚本通过设置汽车编号来设置汽车的颜色。5.否则,实例化一个对手汽车的预制件并且也设置它的汽车编号和颜色。你正在用ParticitantID作为Key来把carController保存到一个字典里。由于你将会收到很多消息格式为“ParticipantID XXX 说怎么样怎么样”类似的内容,所以用字典保存参与者信息,并且后续通过他们的partcipantID来进行查找是一个很简洁的方法6.最后你初始化其他一些游戏常量然后设置_multiplayerReady 为true.表明你已经准备好接收多人游戏的消息。由于无法保证所有的客户端精确的同一时间开始,所以当你还在设置的时候收到游戏内的消息就会发生很奇怪的事情。_multiplayerReady标志会防止出现这样的情况,你在后面就会看到。 你可能会想“如果只有一个对手的话用一个字典去存储我的OpponentCarControllers会不会太麻烦了。为什么不简单地使用opponentCar变量来做这件事?”尽管当前你是在测试两个玩家的情况。但是你会希望你的模型在将来可以适用多于两个玩家的情况,使用字典在这种情况下是一个比较好的方法。回到unity打开MainGame场景。选择GameManager然后在inspector界面设置OpponentCar变量为OpponentPrefab。保存场景,就像下图这样:再次编译运行你的游戏,开始一个多人游戏,然后你会看到一个玩家被设置为黄色车子另一个玩家被设置为蓝色---每个玩家都可以在赛道上行驶自己的车子。在跑道上驾驶 每一辆车,你会注意到一些奇怪的事情:你汽车的进度不会出现在对手的设备上面,反之亦然。是时候让这些设备互相通信了!让汽车移动你可以以同一个频率通知在同一个房间的所有玩家每辆车的位置来移动对手的车辆。当你的游戏收到来自其他客户端的更新消息,你可以适当地移动对手的车辆到它当前的位置。这里有个很重要的问题:这个频率是多久一次?一个经典的电脑游戏大概是每秒10到30次。这相当多!,但是手机设备必须当心电量和数据流量使用限制,所以你最终更新的频率应该小于这个数。开始的时候你可以在每一个Update()里面发送一个网络请求,这个太频繁了但是目前还是可以正常工作,你可以在这个教程的第三部分学习优化网络请求。 发送消息数据在GameController.cs的DomultiplayerUpdate()后面添加下面的代码:1234MultiplayerController.Instance.SendMyUpdate(myCar.transform.position.x,
myCar.transform.position.y,
myCar.rigidbody2D.velocity,
myCar.transform.rotation.eulerAngles.z);这里你发送其他玩家需要显示本地玩家汽车位置的全部信息:他们的x,y坐标,z轴旋转和汽车的当前速度。这个时候unity可能会报错因为你还没有定义SendMyUpdate。但是只要加一点支持代码,就可以很快搞定它。在你的MultiplayerController类开头的声明添加下面的私有变量1private byte _protocolVersion = 1;很快你就会看到这些变量的使用。在private MultiplayerController()的开头添加下面代码来创建你的_updateMessage 列表。1_updateMessage = new List(_updateMessageLength);然后在MultiplayerController 类里面添加SendMyUpdate 123456789101112public void SendMyUpdate(float posX, float posY, Vector2 velocity, float rotZ) {
_updateMessage.Clear ();
_updateMessage.Add (_protocolVersion);
_updateMessage.Add ((byte)'U');
_updateMessage.AddRange (System.BitConverter.GetBytes (posX));
_updateMessage.AddRange (System.BitConverter.GetBytes (posY));
_updateMessage.AddRange (System.BitConverter.GetBytes (velocity.x));
_updateMessage.AddRange (System.BitConverter.GetBytes (velocity.y));
_updateMessage.AddRange (System.BitConverter.GetBytes (rotZ));
byte[] messageToSend = _updateMessage.ToArray();
Debug.Log ("Sending my update message
" + messageToSend + " to all players in the room");
PlayGamesPlatform.Instance.RealTime.SendMessageToAll (false, messageToSend);首先,你把消息放在一个字节数组,最简单的方法,如果不需要很高效率的话,你可以用一个字节列表来创建这个消息。你会复用上一个步骤创建的_updateMessage变量,这个初始化为22字节,对于你想发送的任何消息来说都足够用。在unity里面浮点数占4个字节。你添加的第一个字节是用一个字节来表示协议的版本号。你可以把它当做消息本身的版本号,你后面就会看到这样做的重要性。 下一个字节是发送指令。一个客户端可以发送任何类型的消息给其他玩家,所以首先以一个字符作为你的第一个字节来描述协议余下部分的信息是很有帮助的。在这里你用U来表示你发送的是一个Update。然后你添加传输的信息位置,速度和汽车的角度。AddRange可以添加字节数到列表,System.BitConverter.GetBytes() 可以把这些浮点数转化为一些列字节。接下来你用列表的toArray()方法把列表转为byteArray。最后,你用谷歌游戏库里面的SendMseeageToAll方法来把消息发送给其他玩家,它需要两个参数:消息是否需要可靠发送。要发送的byteArray类型实际数据。第二个参数不需要解释,但是可靠地发送一个消息是什么意思? 可靠还是不可靠准确地说在一个多人游戏里面客户端给其他客户端发送消息有两种方法。不可靠方法是通过UDP协议。当你使用UDP协议发送消息的时候,其中一小部分消息不会到达他们的目标设备。假如他们被接受到也不能保证他们到达的顺序是按照发送的顺序—特别是在你的游戏里面,因为你会频繁地发送更新。可靠的方法是通过TCP协议,它保证一条消息总会被目标设备收到,甚至它还可以保证消息可以按照顺序到达。所以你会问为什么不选择可靠的方法?答案是这些可靠网络消息的便利是以降低传输速度为代价的。可靠消息比不可靠消息要慢,特别是数量多的时候就会很明显。从这个角度想想:因为可靠消息确保消息按照顺序接收,那么如果一个消息没有到达目标呢?没错,你的全部消息都会被推迟丢失的那一个重发并且被收到,这将花费很多时间。大部分游戏一般只使用可靠消息来传送不在意速度的消息。就像我们之前提到的卡牌游戏。实际上许多动作类游戏开发者都尽量避免使用可靠消息。而是根据需要在UDP协议之上实现一些轻量级的逻辑来实现一定程度的可靠性。编译运行你的游戏,加入多人游戏在调试日志你会看到每秒钟你都发出大量的消息1234Sending my update message
System.Byte[] to all players in the roomSending my update message
System.Byte[] to all players in the roomSending my update message
System.Byte[] to all players in the roomSending my update message
System.Byte[] to all players in the room这里不需要这么多杂乱的调试日志。移除SendMyUpdate()里面的Debug.Log输出的消息。现在你知道你的客户端在发送更新消息,你还需要处理从对手客户端发过来的这些消息。比如说沿着跑道移动他们的车辆,接下来你要处理这些事情。 接收消息数据在MultiplayerController.cs用下面代码替换OnRealTimeMessageReceived 里面的代码:123456789
float posX = System.BitC

我要回帖

更多关于 unity3d 网络游戏架构 的文章

 

随机推荐