UNITY 3D –网络游戏编程(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/unity-d-network-game-programming-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 30 分钟阅读 - 14905 个词 阅读量 0UNITY 3D –网络游戏编程(译文)
原文地址:https://www.codeproject.com/Articles/989447/UNITY-D-NETWORK-GAME-PROGRAMMING
原文作者:Vahe Karamian
译文由本站 robot-v1.0 翻译
前言
This article will cover the basics of network programming using Network View in Unity 3D. We will be creating an Authoritative Server based networking environment showcasing the basics functions of network programming using Unity 3D and C#.
本文将介绍在Unity 3D中使用网络视图进行网络编程的基础知识.我们将创建一个基于权威服务器的网络环境,展示使用Unity 3D和C#进行网络编程的基本功能.
介绍(Introduction)
本文是我们文章系列的切线部分,内容涉及Unity 3D中网络编程的基础.独立文章的目的是简化一般主题的解释和代码库.这种方法还将使我能够发布通用的东西,然后您便可以将其用于自己的项目.(This article is a tangent from our article series to cover the basics of network programming in Unity 3D. The purpose of the standalone article is to simplify the explanation and the code base for the general topic. This approach will also allow me to publish something generic which can be then utilized by you for your own projects.) 万一这是第一次阅读Unity 3D文章,以防万一,我列出了以下系列的链接:(Just in case if this is the first time reading the Unity 3D articles, I have listed the links to the series below:)
Unity 3D网络文章:(Unity 3D Networking Article(s):)
-
Unity 3D-网络游戏编程(Unity 3D - Network Game Programming) Unity 3D Leap Motion和Oculus Rift文章:(Unity 3D Leap Motion and Oculus Rift Article(s):)
-
Unity 3D-Leap Motion集成(Unity 3D - Leap Motion Integration) 上面列出的文章将为您提供有关Unity 3D的良好入门基础.(The articles listed above will give you a good starting foundation regarding Unity 3D.) 这篇特定的文章旨在演示并希望解释使用内置网络功能的Unity 3D网络功能.(This particular article is intended to demonstrate and hopefully explain the networking capabilities of Unity 3D using the built in networking capabilities.) **注意:(NOTE:)**还有许多其他第三方解决方案,例如(There are many other third party solutions such a) 光子(Photon) 它将扩展和扩展Unity 3D的网络功能.(that will expand and extend the networking features of Unity 3D.) 涵盖并开始使用"网络视图"的原因是,首先,最重要的是不要依赖第三方,其次,即使您决定采用第三方解决方案,也很容易升级并理解图书馆.(And the reason to cover and start with Network View is to first and fore most not to depend on third parties, and secondly even if you do decide to go with a third party solution, it would be very easy for your to upgrade and also understand the library.)
游戏编程简介:使用C#和Unity 3D(Introduction to Game Programing: Using C# and Unity 3D*) (Paperback)*)(平装)( 要么(or*) (eBook)*)(电子书)( 是为帮助对计算机科学和游戏编程领域感兴趣的个人而设计和开发的.它旨在说明计算机编程的概念和基础.它使用简单游戏的设计和开发来说明和应用这些概念.(is designed and developed to help individuals that are interested in the field of computer science and game programming. It is intended to illustrate the concepts and fundamentals of computer programming. It uses the design and development of simple games to illustrate and apply the concepts.) [ ](http://www.lulu.com/commerce/index.php?fBuyContent=18159441) 书号:9780997148404(ISBN: 9780997148404) 版本:第一版(Edition: First Edition) 发行人:Noorcon Inc.(Publisher: Noorcon Inc.) 英语语言(Language: English) 274页(Pages: 274) 装订:装订完美的平装本(彩色)(Binding: Perfect-bound Paperback (Full Color)) 尺寸(英寸):6宽x 9高(Dimensions (inches): 6 wide x 9 tall) -
|
||电子书(ePUB)(eBook (ePUB))
书号(ISBN):9780997148428(ISBN: 9780997148428)
版本:第一版(Edition: First Edition)
发行人:Noorcon Inc.(Publisher: Noorcon Inc.)
英语语言(Language: English)
大小:9.98 MB(Size: 9.98 MB) |
---|
背景(Background)
假定本文的读者通常熟悉编程概念.还假定读者具有C#语言的理解和经验.还建议本文的读者也熟悉面向对象的编程和设计概念.我们将根据需要在本文中简要介绍它们,但是由于它们完全是单独的主题,因此我们将不对其进行详细介绍.我们还假设您有学习3D编程的热情,并且具有3D图形和矢量数学的基本理论概念.(It is assumed that the reader of this article is familiar with programming concepts in general. It is also assumed that the reader has an understanding and experience of the C# language. It is also recommended that the reader of the article is familiar with Object-Oriented Programming and Design Concepts as well. We will be covering them briefly throughout the article as needed, but we will not get into the details as they are separate topics altogether. We also assume that you have a passion to learn 3D programming and have the basic theoretical concepts for 3D Graphics and Vector Math.) 最后,本文使用Unity 3D版本4.6.1,这是截至初始发布日期的最新公共发行版.该系列中讨论的大多数主题都将与旧版本的游戏引擎兼容,也许还与应该在今年某个时候发布的新版本兼容.但是,有一个主题与当前版本的游戏引擎相比在当前4.6.1版本中有显着差异,那就是UI(用户界面)管道.这是由于引擎中的新UI架构远远优于我们在此版本之前所拥有的.首先,我对新的UI架构感到非常满意.(Lastly, the article uses Unity 3D version 4.6.1 which is the latest public release as of the initial publication date. Most of the topics discussed in the series will be compatible with older versions of the game engine, and perhaps also the new version which is supposed to be release sometime this year. There is however, one topics which is significantly different in the current 4.6.1 version compared to the older version of the game engine, and that is the UI (User Interface) pipeline. This is due to the new UI architecture in the engine which is far superior to what we had prior to this release. I for one, am very happy with the new UI architecture.)
使用代码(Using the code)
下载服务器的项目/源代码:(Downloading the project/source code for server:) 下载Server.zip(Download Server.zip) .(.) 下载客户端的项目/源代码:(Downloading the project/source code for client:) 下载Client.zip(Download Client.zip) .(.) 随着每篇连续的文章的提交,项目/源代码也将不断扩展.新的项目文件和源文件将包括该系列中的较早部分.(With each consecutive article that is submitted, the project/source code will be also expanding. The new project files and source files will be inclusive of older parts in the series.)
迈向多人游戏(Steps towards a Multi-Player Game)
一般来说,联网是一个复杂的主题,在本文中,我们将不涉及打包和拆包网络程序包的详细信息.我们将要讨论的是一种如何通过Unity 3D中可用的网络框架使您的游戏成为多人游戏的方法.(Networking in general is a complex topic, we are not going to get down into the details of packing and unpacking of network packages in this article. What we are going to discuss is the approach on how to enable your game to be multi-player through the networking framework available in Unity 3D.) 游戏设计和编程本身就是一个非常复杂的主题,此外,当您开始考虑多人游戏设计和编程时,事情会变得更加复杂.您的整个设计和体系结构必须围绕将在网络上广播的信息类型进行.如果正在传输的数据具有视觉成分,谁将能够使用信息/数据以及如何以视觉方式表示它.换句话说,您将需要进行大量会计工作!!!(Game design and programming by itself is a pretty complex topic, in addition to that, when you start thinking about multi-player game design and programming, things become even more complex. Your whole design and architecture will have to revolve around what kind of information is going to be broadcasted over the network. Who will be able to consume the information / data and how it will be represented visually if there is a visual component to the data that is being transmitted. In other words you will need to do a lot of accounting!!!) 首先,当您开始项目时,应询问以下问题:(First and foremost, when you start your project, you should question the following:)
-
您的游戏将成为单人吗?(Is your game going to be single player?)
-
您的游戏将成为多人游戏吗?(Is your game going to be multi-player?)
-
您的游戏将同时具有单人和多人组件吗?(Is your game going to have both single and multi-player components?)
提前知道这一点将极大地改变您的游戏设计和架构,并在项目生命周期的后期降低复杂性.从一开始就设计和开发多人游戏支持的游戏要容易得多,然后将现有的单人游戏转换为多人游戏.(Knowing this ahead of time will significantly change your game design and architecture and reduce complexities later on in the project lifetime. It is much easier to design and develop a game for multi-player support from the beginning, then it is to convert an existing single player to a multi-player game later on down the road.) 例如,我们在十篇文章系列中创建的游戏纯粹是针对单个玩家的.而且,我们可能会疯狂地将这个简单的游戏更改为仅适用于多人游戏的多人游戏!因此,让我们考虑一下.(For instance, the game we have been creating during the ten article series has been purely oriented towards a single player. And we might be crazy enough to change this simple game to a multi-player game just for the heck of it! So let’s think about this for a second.) 一些问题要问:(Some questions to ask:)
-
我们为什么还要将您的游戏转换为多人游戏?(Why should we even convert our game into a multi-player game?)
-
在多玩家环境中,游戏机制和逻辑将如何变化?(How will the game mechanics and logic change in a multi-player environment?)
-
需要在网络上共享的一些数据/信息是什么?(What will be some of the data / information that will need to be shared across the network?)
第一个问题的答案很简单:挑战自我!并且也开始讨论有关网络以及多人游戏编程和设计的问题.(The answer to the first questions is pretty simple: to challenge ourselves! And also to start discussion regarding networking and multi-player game programming and design.) 问题2和3将需要更多的计划.(Questions 2 and 3 are going to require a bit more planning.) 但是,在回答最后两个问题之前,我们先来看一下Unity 3D网络和一个独立于我们游戏的一般示例.(But before we even answer the last two questions, let’s look at Unity 3D networking and a general example which will be independent of our game.) 最终,我们的目标是将我们在本文中发现和学习的内容应用于另一篇文章中的游戏.(Eventually, our goal is to apply what we discover and learn in this article to our game in a different article.)
使用Unity 3D网络视图进行网络编程(Network Programming Using Unity 3D Network View)
要了解并启用Unity 3D中的联网功能,您需要了解(To understand and enable networking in Unity 3D, you will need to understand the)网络视图(NetworkView)零件.最好的阅读方法当然是Unity提供的文档.链接在这里:(component. The best place to read about it is of course the documentation provided by Unity. Here is the link:) NetowrkView文档(NetowrkView documentation) .(.) 简而言之,网络视图是跨网络共享数据所涉及的主要组件.它们允许两种网络通信:(In brief, Network Views are the main component involved in sharing data across the network. They allow two kinds of network communication:)状态同步(State Synchronization)和(and)远程过程调用(Remote Procedure Calls).(.) 网络视图会监视特定对象以检测更改.然后将这些更改共享给网络上的其他客户端,以确保状态更改被所有其他客户端记录.这个概念被称为(Network Views keep watch on particular objects to detect changes. These changes are then shared to the other clients on the network to ensure the change of state is noted by all of them. This concept is known as)状态同步(state synchronization)您可以在(and you can read about it further on the) 状态同步(State Synchronization) 页.(page.) 在某些情况下,您不希望在客户端之间进行状态同步的开销,例如,在发送新对象或重新生成的播放器的位置时.由于此类事件很少发生,因此同步所涉及对象的状态是没有意义的.相反,您可以使用远程过程调用来告诉客户端或服务器执行此类操作.有关更多信息(There are some situations where you would not want the overhead of synchronizing state between clients, for example, when sending out the position of a new object or respawned player. Since events like this are infrequent, it does not make sense to synchronize the state of the involved objects. Instead, you can use a remote procedure call to tell the clients or server to perform operations like this. More information about)远程过程调用(Remote Procedure Calls)可以在(can be found on the) RPC(RPC) 手册页.(manual page.)
创建服务器(Creating the Server)
我们将首先为网络演示创建服务器代码.让我们创建一个新的空白unity项目.在项目选项卡的资产下,创建两个新文件夹.应该叫一个(We will start by first creating the server code for our network demonstration. Let’s create a new empty unity project. In your Project tab, under Assets, create two new folder. One should be called)游戏(Game), 和另一个(, and the other)服务器(Server).(.)
图1-项目资产结构(Figure 1-Project Asset Structure)现在,让我们创建第一个脚本.点击(Now let’s create our first script. Click on the)**服务器(Server)**文件夹并创建一个名为的C#脚本(folder and create a C# script called)ServerNetworkManager.cs(ServerNetworkManager.cs).(.) 该脚本将负责为我们的游戏启动和停止服务器.您还将在脚本中看到一些服务器端事件.(This script is going to be responsible for starting and stopping the server for our game. It will also handle some of the server side events are you will see in the script.) 上市(Listing for)ServerNetworkManager.cs(ServerNetworkManager.cs):(:)
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
[RequireComponent(typeof(ServerPlayerManager))]
public class ServerNetworkManager : MonoBehaviour {
private bool serverStarted = false;
public Text lblServerCommandCaption;
public Text lblServerIP;
public Text lblServerPort;
public Transform serverMessagePanel;
public GameObject serverMessageInfo;
private ServerPlayerManager spm;
void Awake()
{
spm = gameObject.GetComponent<ServerPlayerManager>() as ServerPlayerManager; this.serverStarted = false;
if (this.lblServerCommandCaption != null)
{
this.lblServerCommandCaption.text = "Start Server";
}
}
/// <summary>
/// This function was added to perform the new GUI commands
/// </summary>
public void ServerCommand()
{
if (!this.serverStarted)
{
this.startServer();
this.serverStarted = !this.serverStarted;
this.lblServerCommandCaption.text = "Stop Server";
}
else
{
this.stopServer();
this.serverStarted = !this.serverStarted;
this.lblServerCommandCaption.text = "Start Server";
this.lblServerIP.text = "IP Address ...";
this.lblServerPort.text = "Port ...";
}
}
void Update()
{
if(this.serverStarted)
{
if (Network.peerType == NetworkPeerType.Connecting)
{
//GUILayout.Label("Network server is starting up...");
Debug.Log("Network server is starting up...");
}
else
{
//GUILayout.Label("Network server is running.");
Debug.Log("Network server is running.");
showServerInformation();
showClientInformation();
}
}
}
public int listenPort = 25000;
public int maxClients = 5;
private void CaptureServerEvent(string msg)
{
GameObject newButton = GameObject.Instantiate(this.serverMessageInfo) as GameObject;
ServerInfoItem butItem = newButton.GetComponent<ServerInfoItem>();
butItem.lblServerInfo.text = string.Format(msg);
newButton.transform.SetParent(this.serverMessagePanel);
}
public void startServer() {
this.CaptureServerEvent(">>>STARTING SERVER ....");
Network.InitializeServer(maxClients, listenPort, false);
}
public void stopServer() {
Network.Disconnect();
}
public void OnServerInitialized() {
//Debug.Log("Network server initialized and ready");
this.CaptureServerEvent("Network server initialized and ready");
}
public void OnDisconnectedFromServer(NetworkDisconnection info) {
//Debug.Log("Network server disconnected");
this.CaptureServerEvent("Network server disconnected");
}
public void OnPlayerConnected(NetworkPlayer player) {
//Debug.Log("Player " + player + " connected from ip/port: " + player.ipAddress + "/" + player.port);
this.CaptureServerEvent("Player " + player + " connected from ip/port: " + player.ipAddress + "/" + player.port);
spm.spawnPlayer(player);
}
public void OnPlayerDisconnected(NetworkPlayer player) {
//Debug.Log("Player disconnected");
this.CaptureServerEvent("Player disconnected");
spm.deletePlayer(player);
}
// this function will show client information when called ...
public void showClientInformation() {
/*Debug.Log ("Clients: " + Network.connections.Length + "/" + maxClients);
foreach(NetworkPlayer p in Network.connections) {
Debug.Log(" Player " + p + " from ip/port: " + p.ipAddress + "/" + p.port);
}*/
}
public void showServerInformation() {
//GUILayout.Label("IP: " + Network.player.ipAddress + " Port: " + Network.player.port);
if(this.serverStarted)
{
this.lblServerIP.text = string.Format("IP: {0}", Network.player.ipAddress.ToString());
this.lblServerPort.text = string.Format("Port: {0}", Network.player.port.ToString());
}
}
}
让我们看一下脚本,更好地理解它.让我们看一下其中两个功能(Let’s take a look at the script and tunderstand it better. Let’s take a look at two of the functions) startServer()
和(and) stopServer()
.顾名思义,它们用于启动和停止服务器.看一下函数的主体,只有一条我们感兴趣的重要线,那就是以下内容(. As the names indicate, they are used to start and stop the server. Looking at the body of the function, there is only one important line that we are interested in, and that is the following) Network.InitializeServer(maxClients, listenPort, false);
的(The) Network
类是Unity中的主要网络类.功能(class is the main networking class in Unity. The function) InitializeServer()
启动服务器,使用最大数量的客户端以及端口侦听传入的客户端,最后是一个参数,该参数指示服务器是否应该使用NAT打孔使客户端能够与其连接.(starts up the server, taking the maximum number of clients allows as well as the port to listen to for incoming clients, and lastly a parameter indicating whether or not the server should use NAT punchthought to enable clients to connect with it.)
在里面(In the) stopServer()
功能,线(function, the line) Network.Disconnect()
停止服务器.(stops the server.)
在代码的更新版本中,GUI(图形用户界面)已更新为使用新的用户界面框架.这提供了更好的GUI表示,还为我们提供了更大的灵活性,并增强了如何显示所需信息的能力.(In the updated version of the code, the GUI (Graphical User Interface) was updated to use the new User Interface Framework. This provides a better representation of the GUI and also gives us more flexibility and also power on how to display the information we need.)
功能(The function) OnGUI()
已被替换,新的代码库旨在与新的GUI体系结构一起使用.处理GUI命令的新功能是(has been replaced and the new code base is designed to work with the new GUI Architecture. The new function that handles the GUI commands is) ServerCommand()
.它由单个GUI按钮驱动,并检查布尔变量的状态(. It is driven by a single GUI Button and checks the state of a Boolean variable) serverStarted
.如果该值为false,它将通过调用(. If the value is false it will start the server by calling the) startServer()
功能并设置适当的显示标签.如果值为true,则会通过调用来停止服务器(function and set the appropriate display labels. If the value is true, it will stop the server by calling the) stopserver()
并更新相应的显示标签.(function and also update the appropriate display labels.)
**注意:(NOTE:)**我们将在不同的部分讨论UI元素.(We will discuss the UI Elements in a different section.)
的(The)Network.peerType(Network.peerType)可以具有以下值:(can have the following values:)
-
断线(Disconnected)–服务器未初始化,并且没有客户端连接在运行.(– Server is not initialized, and no client connection running.)
-
服务器(Server)–我们正在作为服务器运行.(– We are running as server.)
-
客户(Client)–我们以客户身份运行.(– We are running as client.)
-
连接中(Connecting)–我们正在尝试连接到服务器.(– We are attempting to connect to a server.)
其他功能用于处理网络上的特定事件.(The other functions are used to handle specific events on the network.)
-
OnServerInitialized()(OnServerInitialized())
-
OnDisconnectedFromServer(NetowkrDisconnection信息)(OnDisconnectedFromServer(NetowkrDisconnection info))
-
OnPlayerConnected(NetworkPlayer播放器)(OnPlayerConnected(NetworkPlayer player))
-
OnPlayerDisconnected(NetworkPlayer播放器)(OnPlayerDisconnected(NetworkPlayer player))
前两个是不言自明的,当服务器初始化时,(The first two are self explanatory, when the server is initialized, the) OnServerInitialized()
函数被调用,并且当服务器断开连接时,(function is called and when the server is disconnected, the) OnDisconnectedFromServer(NetowkrDisconnection info)
叫做.此时,与播放器连接和断开连接有关的其他两个功能对我们来说更有意义.(is called. The other two functions having to do with player connecting and disconnecting are of more interest to us at this point.)
当播放器/客户端连接到服务器时,(When a player / client connects to the server, the) OnPlayerConnected(NetworkPlayer player)
叫做.此功能将使用(is called. This function will use the) ServerPlayerManager
类为我们生成新连接的玩家.(class to spawn the newly connected player into the scene for us.)
如果你看看(If you take a look at the) Awake()
函数,我们得到了一个名为(function, we are getting a component called) ServerPlayerManager
附加到持有我们脚本的GameObject并将其分配给(that is attached to the GameObject holding our scripts and assigning it to the) spm
类型的变量(variable which is of type) ServerPlayerManager
.现在,您一定想知道(. Now you must be wondering what the) ServerPlayerManager
类是和确实.(class is and does.)
简而言之,(In short, the) ServerPlayerManager
该类负责:(class is responsible for:)
-
使玩家进入场景;(spawning the player into the scene;)
-
从场景中删除播放器并清理与之相关的所有内容;(deleting the player from the scene and cleaning up everything related to it;)
-
处理玩家的输入;(handling the player input;)
上市(Listing for)ServerPlayerManager.cs(ServerPlayerManager.cs):(:)
using UnityEngine;
using System.Collections;
public class ServerPlayerManager : MonoBehaviour {
public Hashtable players = new Hashtable();
public void spawnPlayer(NetworkPlayer player) {
Debug.Log("Spawning player game object for player " + player);
PlayerInfo ply = GameObject.FindObjectOfType(typeof(PlayerInfo)) as PlayerInfo;
GameObject go = Network.Instantiate(ply.playerInfo, Vector3.up*3, Quaternion.identity, 0) as GameObject;
players[player] = go;
}
public void deletePlayer(NetworkPlayer player) {
Debug.Log("Deleting player game object for player " + player);
GameObject go = players[player] as GameObject;
Network.RemoveRPCs(go.networkView.viewID); // remove buffered Instantiate calls
Network.Destroy(go); // destroy the game object on all clienst
players.Remove(player); // remove player from server list
}
[RPC]
public void handlePlayerInput(NetworkPlayer player, float vertical, float horizontal) {
Debug.Log("Received move from player " + player);
GameObject go = players[player] as GameObject;
if (horizontal > 0)
{
go.transform.Translate(Vector3.forward * Time.deltaTime);
}
else
{
go.transform.Translate(Vector3.back * Time.deltaTime);
}
if (vertical > 0)
{
go.transform.Rotate(Vector3.up, 10 * Time.deltaTime);
}
else
{
go.transform.Rotate(Vector3.up, -10 * Time.deltaTime);
}
}
[RPC]
public void handlePlayerInputV2(NetworkPlayer player, string key)
{
Debug.Log("Received move from player - V2" + player);
GameObject go = players[player] as GameObject;
if (key.Equals("W"))
{
go.transform.Translate(Vector3.forward * Time.deltaTime);
}
if(key.Equals("S"))
{
go.transform.Translate(Vector3.back * Time.deltaTime);
}
if (key.Equals("A"))
{
go.transform.Rotate(Vector3.up, -1);
}
if (key.Equals("D"))
{
go.transform.Rotate(Vector3.up, 1);
}
}
}
所以(So the) spawnPlayer(NetworkPlayer player)
函数根据代表该玩家的预制生成一个玩家.预制件连接到另一个称为(function spawn a player based on a prefab representing that player. The prefab is attached to yet another class called) PlayerInfo
.整个目的(. The whole purpose of the) PlayerInfo
课程是在这一点上为我们的玩家保留预制件.最终,您可以扩展此类以容纳更多信息等.(class is to retain the prefab for our player at this point. Eventually you can expand this class to hold more information and etc…)
首先,我们从附加的GameObject中获取PlayerInfo组件,然后创建一个新的GameObject,它将通过网络保存玩家的实例.如果要通过网络实例化GameObject,则需要使用(First we get the PlayerInfo component from the GameObject that it is attached to, then we create a new GameObject that will hold the instance of the player over the network. If you want to instantiate a GameObject over the network, you will need to usethe) Network.Instantiate(…)
功能!我们使用以下行实例化播放器对象:(function! We instantiate our player object using the following line:)
GameObject go = Network.Instantiate(ply.playerInfo, Vector3.up*3, Quaternion.identity, 0) as GameObject;
最后,我们将对播放器的引用存储到哈希表中.我们使用(Finally we store a reference to the player into our hashtable. We use a)哈希表(Hashtable)跟踪玩家.这样我们就可以正确删除它们以及该特定播放器的相关RPC.(to keep track of the players. This is so that we can properly delete them and also the related RPCs for that particular player.)
当播放器与服务器断开连接时,(When the player disconnects from the server, the) deletePlayer(NetworkPlayer player)
叫做.我们从哈希表中检索玩家参考,并使用(is called. We retrieve the player reference from our hashtable, and use the) Network
类删除所有(class to remove all)RPC(RPC)网络中的相关内容,然后最终通过网络销毁对象,并从哈希表中删除该条目.(related stuff from the network and then finally destroy the object over the network and remove the entry from the hashtable.)
的(The) handlePlayerInput(NetworkPlayer player, float vertical, float horizontal)
功能将提供一种方法,将玩家的位置传达给网络上的每个人.这是一个(function will provide the means to communicate the player’s position to everyone on the network. This is a)RPC(RPC)呼叫.有关更多信息(call. More information about)远程过程调用(Remote Procedure Calls)可以在(can be found on the) RPC(RPC) 手册页.基本上,这里发生的是每个客户端将其位置信息发送到服务器,然后服务器将信息发送给所有客户端.(manual page. Basically what happens here is that each client sends its position information to the server, and then the server transmits the information to all clients.)
的(The) handlePlayerInputV2(NetworkView player, string key)
函数是根据客户端作为字符串值传递给服务器的键输入创建的,以处理玩家的移动.这也是(function is created to handle the player’s movement based on the key input passed by the client to the server as a string value. This is also a)RPC(RPC)呼叫.此功能可让您在将来需要时执行更多命令,例如发射炮弹等.(call. This function will give you the option to perform more commands, like firing cannon balls and etc…, in the future if needed.)
**注意:(NOTE:)**连接上的每个客户端都具有所有玩家的哈希表副本.注意,我们使用哈希表来获取特定玩家的GameObject并将更改应用于该特定玩家.(Every client on the connection has a copy of the Hashtable with all players. Notice, that we use the hashtable to grab the particular player GameObject and apply the changes to that particular player.)
现在我们有了脚本,让我们继续构建场景并进行简单的演示.(Now that we have our scripts, let’s go ahead and build our scene and a simple level for demo.)
创造游戏(Creating the Game)
下一步是我们创建将用于演示目的的场景或关卡.如前所述,关卡的设计和功能将非常简单.(The next step is for us to create our scene or level that will be used for the demonstration purposes. As mentioned previously, the level design and the functionality of what you can do is going to be very simple.) 继续并在场景中创建以下项目:(Go ahead and create the following items in your scene:)
-
平面GameObject.这可以通过从主菜单中选择GameObject-> 3D Object-> Plane来完成.(A plane GameObject. This can be done by selecting GameObject->3D Object->Plane from the main menu.)
-
主摄像机应该已经存在于场景中.如果没有使用相同的方法创建一个:从主菜单中选择GameObject-> camera.(A Main Camera should already be present in your scene. If not create one using the same method: select GameObject->camera from the main menu.)
-
定向光,您可以通过从主菜单中选择GameObject-> Light-> Directional Light来实现.(A Directional Light, you can achieve this by selecting GameObject->Light->Directional Light from the main menu.)
-
一个名为GameController的空游戏对象.(An Empty Game Object called GameController.)
**注意:(NOTE:)**如果您不熟悉GameObject的创建和/或Unity IDE,建议您阅读文章系列,或者自己做一些实验以了解基础知识.(If you are not familiar with GameObject creations and or the Unity IDE, I suggest you read the article series and or do some experimentation yourself to get an understanding of the basics.)
完成上述步骤后,“层次结构窗口"应该看起来像这样:(Your Hierarchy Window should look something like this after you are done with the steps above:)
图2场景层次服务器端(Figure 2-Scene Hierarchy Server Side)将场景保存在(Save the scene under the)游戏(Game)您之前创建的文件夹.调用现场游戏.(folder you had created previously. Call the scene Game.) 下一步创建(Next create)立方体(Cube)原语,并将以下组件附加到Cube GameObject:(primitive, and attach the following components to the Cube GameObject:)
-
网络视图组件(Network View component)
-
刚体组件(Rigid Body component)
这可以通过选择(This can be achieved by selecting the)立方体游戏对象(Cube GameObject),并使用(, and using the)检查器窗口(Inspector Window), 选择(, select)添加组件(Add Component)并找到所需的列出的组件.(and find the listed components that are needed.)
保留"刚体"和"网络视图"组件的默认值不变.(Leave the default values for both the Rigid Body and Network View components as they are.)
做一个(Make a)预制件(Prefab)通过将其拖动到(of the Cube by dragging it into the)游戏(Game)夹.预制件已创建.您现在可以从场景中删除多维数据集.我们将不再需要它.(folder. A prefab has been created. You can delete the Cube from your scene now. We will not be needing it anymore.)
如果您回想起上一节,我们需要另一个脚本来处理(If you recall from the previous section, we need another script to handle out) PlayerInfo
.该脚本也将放置在(. This script will be also placed in the)**游戏(Game)**夹.继续创建一个新的C#脚本并调用它(folder. Go ahead and create a new C# script and call it)PlayerInfo.cs(PlayerInfo.cs).(.)
上市(Listing for)PlayerInfo.cs(PlayerInfo.cs):(:)
using UnityEngine;
using System.Collections;
public class PlayerInfo : MonoBehaviour {
public GameObject playerInfo;
}
它只是一个引用我们预制件的脚本.您的最终环境在视觉上应如下所示:(It is simply just a script that will reference our Prefab. Your final environment should look something like this visually:)
图3场景视图(Figure 3-Scene View)至此,我们拥有了所有资产.现在,我们需要正确配置它们,以便获得所需的结果.(At this point we have all of our Assets. Now we need to configure them properly so that we get the results we are looking for.)
选择GameController(Select the GameController) GameObject
,并附上(, and attach the) PlayerInfo
和(and) ServerNetworkManager
脚本到GameController.还将网络视图组件添加到GameController GameObject.(scripts to the GameController. Also add the Network View components to the GameController GameObject.)
请注意,当您附加(Notice, that when you attach the) ServerNetworkManager
脚本(script to the) GameController
,(, the) ServerPlayerManager
也自动附加,这是因为在我们的(is also automatically attached, that is because in our)ServerNetworkManager(ServerNetworkManager)我们已经宣布(we have declared that the) ServerNetworkManager
需要(requires the) ServerPlayerManager
通过在类声明之前放置以下命令:(by placing the following command before the class declaration:) [RequireComponent(typeof(ServerPlayerManager))]
GameController上的最后一项配置是将"网络视图"的属性更改为以下内容:(One last configuration on the GameController would be to change the attributes of the Network View to the following:)
-
状态同步(State Synchronization)应该设置为(should be set to)关(Off)
-
观测到的(Observed)应该设置为(should be set to)没有(None)
几乎忘了,还有最后一个配置需要完成.请注意(Almost forgot, there is one more last configuration that needs to be done. Notice that the) PlayerInfo
组件具有用于播放器预制件的占位符.这是我们的立方体预制件将要连接的地方,所以继续拖放(component has a place holder for our player prefab. This is where our cube prefab is going to be attached, so go ahead and drag and drop the)立方体(Cube)预制进(prefab into the)玩家信息栏(Player Info slot).(.)
如果正确完成所有操作,则现在可以实际测试服务器代码.继续运行程序.您将看到以下屏幕:(If all is done correctly, you can now actually test out the server code. Go ahead and run the program. You will see the following screen:)
图4-运行服务器代码(Figure 4-Running Server Code)如您所见,GUI指出服务器未运行,并且正在显示一个按钮供我们启动服务器.继续并单击启动服务器按钮.(As you can see our GUI states that the server is not running and it is displaying a button for us to start the server. Go ahead and click the Start Server button.)
图5-服务器初始化后的屏幕(Figure 5-Screen after Server was Initialized)
图6-控制台显示我们的调试消息(Figure 6-Console displaying our Debug Messages)如您所见,我们已经启动了服务器,并且服务器已成功初始化.现在,我们可以将注意力转向客户代码.(As you can see we have started our server and it has been successfully initialized. Now we can turn our attention to our Client code.)
创建客户(Creating the Client)
我们将为客户启动一个新项目.是的,我在很长一段时间后意识到,最好将服务器和客户端代码分开.它不仅使生活更轻松,而且对于初学者来说也有助于解释.(We are going to start a new project for the client. Yes, I realized after a very long time that it is best to keep the server and the client code separated. It just makes life easier, and helps explanation also easier for starters.) 但是,即使这是一个新项目,也必须与Server项目完全相同.有一些差异.因此,我们要做的第一件事就是创建目录结构.因此,继续创建两个文件夹:(However, even though this is a new project, it will have to be the exact duplicate of the Server project. With a few differences. So the first thing we are going to do is create our directory structure. So go ahead and create the two folders:)
-
客户(Client)
-
游戏(Game)
Game文件夹将与服务器项目中的Game文件夹完全一样,因此请帮个忙,并将Game文件夹内容从服务器项目复制并粘贴到客户端项目Game文件夹中.(The Game folder is going to be exactly like the Game folder in the server project, so do yourself a favor and copy and paste the Game folder content from the server project into the client project Game folder.) 现在,让我们创建客户端脚本.在"客户端"文件夹中,继续创建一个新的C#脚本并命名它(Now let’s create the Client scripts. In the Client folder go ahead and create a new C# script and name it)ClientManger.cs(ClientManger.cs).的(. The)客户经理(ClientManager)您猜对了,客户负责.它是处理与服务器的连接的代码,也是客户端发送至服务器的更新等的代码.(is responsible for the, you guessed it, the client. It is the code that handles the connection to the server and also the updates that the client sends to the server and so forth.) 上市(Listing for)ClientManager.cs(ClientManager.cs):(:)
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class ClientManager : MonoBehaviour {
private bool clientStarted = false;
public Text lblClientCommandCaption;
public Text lblClientIP;
public Text lblClientPort;
public Transform clientMessagePanel;
public GameObject clientMessageInfo;
/// <summary>
/// This function was added to perform the new GUI commands
/// </summary>
public void ClientCommand()
{
if (!this.clientStarted)
{
this.connectToServer();
this.clientStarted = !this.clientStarted;
this.lblClientCommandCaption.text = "Disconnect";
}
else
{
this.disconnectFromServer();
this.clientStarted = !this.clientStarted;
this.lblClientCommandCaption.text = "Connect to Server";
this.lblClientIP.text = "IP Address ...";
this.lblClientPort.text = "Port ...";
}
}
private void CaptureClientEvent(string msg)
{
GameObject newButton = GameObject.Instantiate(this.clientMessageInfo) as GameObject;
ClientInfoItem butItem = newButton.GetComponent<ClientInfoItem>();
butItem.lblClientInfo.text = string.Format(msg);
newButton.transform.SetParent(this.clientMessagePanel);
}
void Update() {
if(Input.anyKey) {
sendInputToServer();
}
}
public void sendInputToServer() {
if(Input.GetKey(KeyCode.W))
networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "W");
if(Input.GetKey(KeyCode.S))
networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "S");
if (Input.GetKey(KeyCode.A))
networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "A");
if (Input.GetKey(KeyCode.D))
networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "D");
}
[RPC]
public void handlePlayerInput(NetworkPlayer player, float vertical, float horizontal) {
}
[RPC]
public void handlePlayerInputV2(NetworkPlayer player, string key)
{
}
public string remoteIP = "127.0.0.1"; // NOTE: You will replace with your server IP
public int remotePort = 25000;
public void connectToServer() {
//Debug.Log("Tyring to connect to Server...");
this.CaptureClientEvent("Tyring to connect to Server...");
Network.Connect(remoteIP, remotePort);
}
public void disconnectFromServer() {
//Debug.Log("Tyring to disconnect from the Server...");
this.CaptureClientEvent("Tyring to disconnect from the Server...");
Network.Disconnect();
}
public void OnConnectedToServer() {
//Debug.Log("Successfully connected to server as player " + Network.player);
this.CaptureClientEvent("Successfully connected to server as player " + Network.player);
this.lblClientIP.text = string.Format("IP: {0}", Network.player.ipAddress.ToString());
this.lblClientPort.text = string.Format("Port: {0}", Network.player.port.ToString());
}
public void OnDisconnectedFromServer (NetworkDisconnection info) {
if (info == NetworkDisconnection.LostConnection)
//Debug.Log("Lost connection to the server");
this.CaptureClientEvent("Lost connection to the server");
else
//Debug.Log("Disconnected from the server");
this.CaptureClientEvent("Disconnected from the server");
GameObject[] gos = GameObject.FindGameObjectsWithTag("Player");
foreach(GameObject go in gos) {
Destroy(go);
}
}
public void OnFailedToConnect (NetworkConnectionError error) {
//Debug.Log("Failed to connect to Server: " + error);
this.CaptureClientEvent("Failed to connect to Server: " + error);
}
}
如您所见,代码的结构与(As you can see the structure of the code is very similar to the) ServerNetworkManager
码.但是有一些区别.让我们来看看差异.(code. But there are some differences. Let’s take a look at the differences.)
以下是已定义功能的列表:(Here are a list of the functions that have been defined:)
-
connectToServer()
-
disconnectFromServer()
-
OnConnectedToServer()
-
OnDisconnectedFromServer(NetworkDisconnection info)
-
OnFailedToConnect(NetworkConnectionError error)
-
handlePlayerInput(NetworkPlayer player, float vertical, float horizontal)
-
sendInputToserver()
-
Update()
功能(The functions) connectToServer()
使用(uses the) Network.Connect()
功能以连接到指定的服务器.的(function to connect to the specified server. The) disconnectFromServer()
功能用途(function uses) Network.Disconect()
与服务器断开连接.的(to disconnect from the server. The) OnConnectedToServer()
功能只会泄露谁已连接到服务器.的(function just spills out who has been connected to the server. The) OnFailedToConnect()
函数溢出了为什么我们无法连接到服务器的调试错误.(function spills out the debug error of why we could not connect to the server.)
的(The) OnDisconnectedFromServer (NetworkDisconnection info)
功能至关重要,因为它在客户端进行清理.找到所有(function is crucial because it does the clean-up on the client side. It finds all) GameObjects
使用名为Player的标签,它将它们从本地场景中销毁.(with the tag called Player and it destroys them from the local scene.)
的(The) Update()
功能检查是否按下了任何键,然后调用(functions check to see if any key was pressed, and it calls the) sendInputToServer()
得到的功能(function which gets the)垂直(Vertical)和(and the)卧式(Horizontal)轴,如果它们不等于0,则调用(axis and if they are not equal to 0, then it calls an)RPC(RPC)使用以下代码行通过网络连接到服务器:(function over the network to the Server using the following line of code:)
networkView.RPC(” handlePlayerInputV2",RPCMode.Server,Network.player,字符串键);(networkView.RPC(“handlePlayerInputV2”, RPCMode.Server, Network.player, string key);)
RPC函数是可以使用网络视图组件通过网络调用的函数.它具有三个参数:(An RPC function is a function that can be called over the network using the Network View component. It has three parameters:)
-
RPC函数名称– handlePlayerInput(RPC Function Name – handlePlayerInput)
-
RPC模式–在这种情况下,我们仅将此消息通过RPCMode.Server发送到服务器(RPC Mode – In this case we are only sending this message to the server with RPCMode.Server)
-
函数参数:Network.player,垂直和水平(Function Parameters: Network.player, vertical and horizontal)
客户端配置(Configuration for the Client)
现在,我们也已经编写了客户脚本.我们需要对(Now that we have our client scripts written as well. We need to make a modification on the)游戏控制器(GameController)我们从Server项目复制过来的GameObject.选择GameController,然后删除服务器脚本:(GameObject we copied over from the Server project. Select the GameController, and remove the Server scripts:)**ServerNetworkManager.cs(ServerNetworkManager.cs)**和(and)ServerPlayerManager.cs(ServerPlayerManager.cs).(.) 现在附上(Now attach the)客户经理(ClientManager)脚本给你(script to your)游戏控制器(GameController)在里面(in the)客户(Client)项目.您现在应该只拥有(project. You should now only have the)**PlayerInfo.cs(PlayerInfo.cs)**和(and)**ClientManager.cs(ClientManager.cs)**附加到的脚本(scripts attached to the)游戏控制器(GameController).(.)
测试我们的客户端/服务器代码(Testing Our Client / Server Code)
为了测试我们的客户端和服务器代码是否可以协同工作,您将需要启动两个单独的Unity 3D实例,并打开一个服务器代码,另一个打开客户端代码.(In order to test our client and server code working together you will need to start two separate instances of Unity 3D and have one open the server code and the other the client code.)
假设您已启动并运行服务器代码,现在我们可以启动我们的客户端代码.首次运行客户端时,将显示以下内容:(Assuming that you have your server code up and running, now we can start up our client code. The following what will appear when you first run the client:)
图7-客户端正在运行-未连接到服务器(Figure 7-Client Running - Not Connected to Server)让我们继续,选择"连接到服务器"按钮,看看会发生什么.(Let’s go ahead and select the Connect to Server button and see what happens.)
图8-客户端连接到服务器-服务器生成了播放器预制件(Figure 8-Client Connected To Server - Server Spawns the Player Prefab)客户端控制台:(Client Side Console:)
图9-客户端控制台(Figure 9-Client Side Console)服务器端控制台:(Server Side Console:)
图10-服务器端控制台(Figure 10-Server Side Console)如您所见,我们的代码按预期执行.停止"客户端"项目,然后继续并构建"客户端"的独立可执行文件.选择文件->构建…(As you can see our code is performing as expected. Stop the Client project, and let’s go ahead and Build a stand-alone executable of our Client. Select File->Build…)
图11-Unity 3D构建界面(Figure 11-Unity 3D Build Interface)选择PC,Mac和Linux Standalone,然后选择您的平台.确保在"播放器设置"中选中了"在后台运行",然后构建项目.(Select the PC, Mac & Linux Standalone and choose your platform. Make sure that in your Player Settings, Run In Background is checked, and Build your project.)
将多个客户端连接到服务器(Connecting Multiple Clients to the Server)
使用新创建的服务器可执行文件来执行和运行服务器.我们还将使用客户端可执行文件启动三个客户端,以在多人模拟中演示服务器/客户端关系.(Use the newly created executable for the server to execute and run the server. We will also start three clients using the client executable to demonstrate the server / client relationship in a multiplayer simulation.)
启动服务器,通过执行刚构建的可执行文件来启动客户端.选择最低的分辨率,然后选中"窗口化"复选框字段,这样我们就不会占用过多的屏幕空间.单击播放.客户端启动,我们得到"连接到服务器"按钮,继续进行选择.(Start the server, start a client by executing the executable you just build. Select the lowest resolution and check the Windowed checkbox field so that we do not take too much screen real estate. Click Play. The client start, and we get the Connect to Server button, go ahead and select it.)
屏幕现在应如下所示:(Your screen should now look something like the following:)
图12-服务器在后台运行,并附加了客户端.(Figure 12-Server running in the background, with the client(s) attached.)继续并使用客户端,使用键盘的箭头键移动多维数据集(播放器).请注意,多维数据集在两个屏幕上均移动.还要注意服务器上的控制台.我们正在捕获谁在实际采取行动!(Go ahead and using the Client, move the Cube (the Player) by using the keyboard arrow keys. Notice that the Cube moves on both screens. Also notice the Console on the server. We are capturing who is actually making the move!)
图13-服务器控制台显示移动输入(Figure 13-Server Console displaying movement input)
图14-服务器在后台运行,并且三个客户端在前台(Figure 14-Server Running in the Background, and the three clients are in the Foreground)另请注意,服务器控制台已使用新的连接信息进行了更新.而已!现在我们有了基本(Also notice the Server console has updated itself with the new connection information. That’s it! We now have our basic)权威服务器网络基础(Authoritative Server Networking Foundation)已实施.现在您已经了解了基础知识,希望您可以在此基础上进行扩展,并创建更加复杂和令人印象深刻的多玩家游戏.(implemented. Now that you know the basics, hopefully you can expand on it and create more complex and impressive multi-player games.)
图14C-Server在后台运行,并且三个客户端位于前台(Figure 14C-Server Running in the Background, and the three clients are in the Foreground)请注意,当我们停止服务器时,所有其余连接到服务器的客户端都会自动断开连接.(Notice that when we stop the server, all remaining clients that are connected to the server get disconnected automatically.)
兴趣点(Points of Interest)
所述的网络编程通常是复杂且繁琐的.创建一个多人游戏是一项非常具有挑战性的任务,通常必须花很多时间来学习该主题.然后,您将需要花费大量时间来学习Unity 3D提供的网络设计和架构,这可能是另外一个里程碑.(Network programming as stated is complex and very tedious in general. Creating a Multi-Player game is a very challenging task and one must spend a lot of time learning about the topic in general. Then you will need to spend a lot of time learning about the Networking design and architecture provided by Unity 3D which could be another mile stone.) 本文基于我研究过的几篇文章,以学习Unity 3D网络机制,并希望它将帮助那里的某人更快地提高速度!(This article was based on several articles which I have researched to learn the Unity 3D networking mechanism and hopefully it will help someone out there to come up to speed faster than I did!) 对于那些一直关注Unity 3D文章系列的人,我将尝试看看我们是否可以创建我们的游戏Gold Rush的多人游戏版本!(For those who have been following the Unity 3D Article Series, I will be trying to see if we can create a multi-player version of our game Gold Rush!)
Unity 3D文章系列(Unity 3D Article Series)
-
Unity 3D –游戏编程–第10部分(Unity 3D – Game Programming – Part 10)
Unity 3D网络文章:(Unity 3D Networking Article(s):)
-
Unity 3D-网络游戏编程(Unity 3D - Network Game Programming) Unity 3D Leap Motion和Oculus Rift文章:(Unity 3D Leap Motion and Oculus Rift Article(s):)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET Mobile iPhone Windows VS2010 Visual-Studio Design Dev game 新闻 翻译