Unity3d多人在线教程

http://blog.sina.com.cn/s/blog_90e888f501019jmw.html


Unity多人在线教程Unity3d多人在线教程_第1张图片

欢迎Unity用户,在这儿你将学习网络的使用。

希望你们过得愉快并且希望这个教程对你们有所帮助。

1。简介

这个教程的目的是展示Unity网络功能,我们将告诉你如何创建一个基本的以及用Master服务器/客户端,UDP服务器/客户端增强的网络应用程序来进行网络连接。在这个教程里我们将使用Unity iPhone 1.6, iPhone 3GS和来自于官网的Startrooper的案例。

2。开始

在开始前我先要了解一些知识点:

Unity3d多人在线教程_第2张图片用于基本的和网络强化的组件。

Unity3d多人在线教程_第3张图片创建一个服务器和客户端

Unity3d多人在线教程_第4张图片使用Master Server

Unity3d多人在线教程_第5张图片使用直接连接的方法

Unity3d多人在线教程_第6张图片使用UDP广播服务器

Unity3d多人在线教程_第7张图片在网络上创建一个简单的场景

Unity3d多人在线教程_第8张图片把StartTrooper游戏变成多人在线游戏

Unity3d多人在线教程_第9张图片使用其它的Unity组件以及更多其它功能

你应该有:

Unity3d多人在线教程_第10张图片Unity iPhone 1.6.

Unity3d多人在线教程_第11张图片iPhone或者iPod

Unity3d多人在线教程_第12张图片有关网络和他们如何工作方面的知识。

Unity3d多人在线教程_第13张图片高级的C#和JavaScript应用能力。

Unity3d多人在线教程_第14张图片基本的Unity的使用技巧,你可以从这儿找到更多的信息

注意事项:

Unity3d多人在线教程_第15张图片Untiy支持.NET 1.1和2.1

你可以通过:Edit->Project Settings->Player->Enable Unity Networking  来开启或关闭网络功能。

Unity3d多人在线教程_第16张图片Unity网络支持wifi,3G和GSM链接。

Unity3d多人在线教程_第17张图片你能连接两个不同的Unity平台,例如,你可以从Unity桌面链接到Unity iPhone,从Unity Web播放器链接到Unity iPhone,等等。

3。创建你的第一个用户/服务器程序

在这个章节里,我们将创建包括一个简单的多人在线的基础应用程序,在网络上创建第一个移动物体和基本的客户端和服务器端的交互的例子。这个例子我们使用了似有的多人在线组件。例如:Network和NetworkView.我们将用直接连接的方法连接客户端和服务器。

3。1。准备场景

现在让我们从一个简单的场景开始,你的第一步:

Unity3d多人在线教程_第18张图片创建一个新工程。

Unity3d多人在线教程_第19张图片创建一个新的预制物体然后给它命名:Player

Unity3d多人在线教程_第20张图片创建一个新的方块物体

Unity3d多人在线教程_第21张图片把你的Hierarchy面板的方块物体拖到预制物体上,然后从场景中删除这个方块。

Unity3d多人在线教程_第22张图片创建一个新的Plane然后给它命名:Ground。并设置:Position(0,0,0),Rotation(0,0,0),Scale(5,5,5).

Unity3d多人在线教程_第23张图片创建一个平行灯光,并设置:Position(0,15,0),Rotation(25,0,0),Scale(1,1,1),Shadows->Type->Soft Shadows

最后,保存你的场景并命名:MainGame.

现在看起来应该像这样子:Unity3d多人在线教程_第24张图片
3。2。创建脚本并添加脚本

接下来你将学习如何创建服务器和客户端,在网络中实例化场景和物体,移动物体以及把所有的对象连接起来。

3。2。1。服务器和客户端

我们将开始最重要的部分——服务器和客户端的创建。

Unity3d多人在线教程_第25张图片创建一个新的JavaScript文件然后命名它为ConnectionGUI

Unity3d多人在线教程_第26张图片把这个脚本文件拖给层级面板里的Main Camera(主摄像机),然后打开这个文件创建一些变量:

var remoteIP="127.0.0.1";

var remotePort=25000;

var listenPort=25000;

var useNAT=false;

var yourIP="";

var yourPort="";

 

Unity3d多人在线教程_第27张图片现在我们将用Unity GUI为服务器创建一个接口然后连接它。


function OnGUI()

{

    //检查你是否连接到服务器。

   if(Network.peerType==NetworkPeerType.Disconnected)

   {

      if(GUI.Button(new Rect(10,50,100,30),"Connect"))

      {

         Network.useNat=useNAT;

         //连接到服务器

         Network.Connect(remoteIP,remotePort);

       }

       if(GUI.Button(new Rect(10,50,100,30),"Start Server"))

       {

           Network.usenat=useNAT;

           //创建服务器

           Network.InitializeServer(32,listenPort);

           //通知我们场景中的物体network已经准备好了。

           for(var go:GameObject in FindObjectsOfType(GameObject))

           {

              go.SendMessage("OnNetworkLoadedLevel",SendMessageOptions.DontRequireReceiver);

           }

       }

        //添加IP地址和端口号

      remoteIP=GUI.TextField(new Rect(120,10,100,20),remoteIP);

      remotePort=parseInt(GUI.TextField(new Rect(230,10,40,20),remotePort.ToString()));

      }

       else

      {

         //得到你的IP地址和端口

         ipaddress=Network.player.ipAddress;

         port=Network.player.port.ToString();

         GUI.Label(new Rect(140,20,250,40),"IP Address:"+ipaddress+":"+port);

         if(GUI.Button(new Rect(10,10,100,50),"Disconnect"))

         {

              //从服务器上断开连接

              Network.Disconnect(200);

         }

    }

}

注意下面的函数,这个函数当有人连接成功后时被调用。同时通知场景中所有对象网络已经准备好了。

function OnConnectedToServer()

{

     //通知场景中的物体网络已经准备好了

     for(var go:GameObject in FindObjectsOfType(GameObject)) 

     {

         go.SendMessage("OnNetworkLoadedLevel",SendMessageOptions.DontRequireReceiver);    

     }

}

Play模式你的屏幕应该是这样的:Unity3d多人在线教程_第28张图片

Unity3d多人在线教程_第29张图片

现在可以测试你的服务器和用户端了。设置:Player Settings(Edit->Project Settings->Player)去设置你的iPhone Bundle Identifier并且切换默认的Screen Orientation为Landscape Right。编译你的工程到iPhone并且在编辑器中创建一个服务器。尝试用在服务器屏幕中找到的IP地址连接到服务器。如果一切正常的话。你将能看到“Disconnect”按钮然后你的IP地址同时出现在两屏幕上。注意两个应用程序必须在相同的网络上。

 

3。2。2。在网络上实例化场景和物体。

现在我们需要在Player(Cube)然后[写代码去实例化它:

Unity3d多人在线教程_第30张图片选择Player预制物体然后添加一个NetworkView:

Unity3d多人在线教程_第31张图片改变组件的同步状态Synchronization参数为Reliable Delta Compressed.这是需要你给所有的用户显示同步。

Unity3d多人在线教程_第32张图片添加一个Rigidbody给你Player预制。

现在在网络上实例化你的Player和物体:

Unity3d多人在线教程_第33张图片创建一个新的空的GameObjecj然后命名为:Spawn.Object参数:Position(0,5,0),Rotation(0,0,0),Scale(1,1,1)。

Unity3d多人在线教程_第34张图片创建一个新的JavaScript文件然后命名Instantiate

Unity3d多人在线教程_第35张图片打开文件写下以下代码:

var SpaceCraft:Transform;

 

function OnNetworkLoadedLevel()

{

     //当网络加载后实例化SpaceCraft。

     Network.Instantiate(SpaceCraft,transform.position,transform.rotation,0);

}

 

function OnPlayerDisconnected(player:NetworkPlayer)

{

     Network.RemoveRPCs(player,0);

     Network.DestroyPlayerObjects(player);

}

 

Unity3d多人在线教程_第36张图片把这个脚本拖放到Spawn物体上。

Unity3d多人在线教程_第37张图片选择Spawn物体然后拖放到Player参数到"Player(Transform)"变量上。

 

现在测试游戏,你将会看到服务器和每一个连接到的用户将会有他们的Player(Cube)。我们来创建一个简单的测试:

Unity3d多人在线教程_第38张图片创建一个新的JavaScript文件并命名Control.

Unity3d多人在线教程_第39张图片添加这段代码

function OnGUI()

{

     if(GUI.Button(new Rect(20,100,50,50),"up"))

     {

           GameObject.Find("Player(Clone)").transform.position=new Vector3(0,5,0);

     }
}

 

Unity3d多人在线教程_第40张图片把这个文件拖放到Spawn物体上

Unity3d多人在线教程_第41张图片编译这个工程,创建一个服务器并且连接它。现在你可以看到每一个客户端有发球自己的Player(Cube)。

你的编辑屏幕应该看起来像这样:

Unity3d多人在线教程_第42张图片

小结

恭喜你!你已经学会了创建了一个多人程序所必要的基础知识。

Unity3d多人在线教程_第43张图片为多人游戏准备一个基本的场景

Unity3d多人在线教程_第44张图片创建一个服务器。

Unity3d多人在线教程_第45张图片创建一个客户端。

Unity3d多人在线教程_第46张图片使用直接连接。

Unity3d多人在线教程_第47张图片使用基本的network组件。

Unity3d多人在线教程_第48张图片在newwork上实例化一个场景和物体。

Unity3d多人在线教程_第49张图片把这些都连接在一起。

Download the whole NetworkExampla project:

http://asprofas.xz.lt/mp_documentation/images/NetworkExsample.zip

下一个章节,我们将在包括及创建一个高级的网络设置的思路上进行拓展。

4。利用Startrooper这个游戏执行多人在线的创建。

在这个章节里,你将学会如何把StarTrooper游戏由单人变成多人游戏。我们将用复杂的组件和三种不同的连接方式:直接连接(在第四章),MasterServer连接和UDP广播连接。在最后这个章节里你将在多人模式下有能力去环绕飞行并且击败其它的用。

从Unity官方网站下载StarTrooper并且弄明白它:

http://asprofas.xz.lt/mp_documentation/images/mpStarTrooper.zip

4。1。改变场景

我们将要把这个单人游戏修改一下,后面会进一步深入修改。

Unity3d多人在线教程_第50张图片打开下载好的StarTrooper工程文件。

Unity3d多人在线教程_第51张图片选择StarTrooper场景

Unity3d多人在线教程_第52张图片在Hierarchy面板里选择SpaceCraftFBX文件。

Unity3d多人在线教程_第53张图片添加一个NetworkView组件。

Unity3d多人在线教程_第54张图片从Player上移除控制脚本。

Unity3d多人在线教程_第55张图片从对象移除Missile Launcher脚本。

Unity3d多人在线教程_第56张图片给对象上添加一个尾迹渲染器。

Unity3d多人在线教程_第57张图片设置尾迹渲染器Materials->Element 0->missleTracer

Unity3d多人在线教程_第58张图片创建一个新的空物体并命名Spawn.设置Transform参数:Position(0,30,11),Rotation(0,0,0),Scale(1,1,1).

4。2。整合

现在我们将要改变并且整合网络到这个工程的。让我们为网络准备这个场景。完成后我们将创建一个服务器和客户端。

4。2。1。整合物体和场景

现在我们需要创建一个脚本为的是在网络中转换我们的刚体:

Unity3d多人在线教程_第59张图片首先,这我们的新脚本创建两个文件夹分别是:“NetworkFiles”和“Plugins”这个是存入C#文件的。

Unity3d多人在线教程_第60张图片创建一个C#文件并命名它为NetworkRigidbody.

把NetworkRigidbody.cs文件拖放到Plugins文件夹。

Unity3d多人在线教程_第61张图片别管这个文件是怎么回事,只要明白要需要它的地方就能用得上就可以了。

Unity3d多人在线教程_第62张图片你可以把这个脚本用在任何你想要的工程中,因为它适合所有的刚体对象。

Unity3d多人在线教程_第63张图片打开你的NetworkRigidbody.cs文件并输入以下代码:


using UnityEngine;

using System.Collections; 

public class NetworkRigidbody : MonoBehaviour

{

   public double m_InterpolationBackTime = 0.1;

   public double m_ExtrapolationLimit = 0.5; 

   internal struct  State

   {

      internal double timestamp;

      internal Vector3 pos;

      internal Vector3 velocity;

      internal Quaternion rot;

      internal Vector3 angularVelocity;

    } 

     //我们储存20个状态用“playback”信息 

    State[] m_BufferedState = new State[20]; //与用到的slots(插槽)保持联系。

    int m_TimestampCount; 

 

    void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)

    {

        // 发送数据给服务器 

        if (stream.isWriting)

        {

             Vector3 pos = rigidbody.position;

             Quaternion rot = rigidbody.rotation;

             Vector3 velocity = rigidbody.velocity;

             Vector3 angularVelocity = rigidbody.angularVelocity;

 

             stream.Serialize(ref pos);

             stream.Serialize(ref velocity);

             stream.Serialize(ref rot);

             stream.Serialize(ref angularVelocity);

         }

         // 从远程客户端读取数据。

         else

         {

             Vector3 pos = Vector3.zero; 

             Vector3 velocity = Vector3.zero; 

             Quaternion rot = Quaternion.identity;

             Vector3 angularVelocity = Vector3.zero;

             stream.Serialize(ref pos);

             stream.Serialize(ref velocity);

             stream.Serialize(ref rot);

             stream.Serialize(ref angularVelocity);

             //切换buffer另一边,删除状态20

             for (int i=m_Buffered State.Length-1;i>=1;i--)

             {

                  m_BufferedState[i] = m_BufferedState[i-1];

              } 

              //  在slot 0记录当前状态              

              State state;

              state.timestamp = info.timestamp;

              state.pos = pos;

              state.velocity = velocity;

              state.rot = rot;

              state.angularVelocity = angularVelocity;

              m_BufferedState[0] = state;

              

              //更新用slot计数,然后永远不超出buffer的大小。

              // Slot不是真正的释放,这仅仅是确认buffer是填满了并且未初始化的slot是否

              //没有用

               m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);  

               // 检查状态是否正常,如果这个是不协调的你可以重新调整或

               //者丢掉非正常的状态。这里没有做什么。

               for (int i=0;i

               {

                    if (m_BufferedState[i].timestamp < m_BufferedState[i+1].timestamp)

                         Debug.Log("State inconsistent");

                }

            }

         }

        // 我们有一个interpolationBackTime的窗口在我们本质上播放

        // 通过有interpolationBackTime普通的ping,你将通常用来填写。

        // 并且仅仅没有更过的数据到达我们将用的额外polation(位置?)。

        void Update ()

        {

             //这个是刚体的目标playback time。

             double interpolationTime = Network.time - m_InterpolationBackTime;  

              // 使用interpolation如果目标playback time是呈现在buffer里面

             if (m_BufferedState[0].timestamp > interpolationTime)

             {

                  //检查buffer并且查找正确的状态去play back。

                  for (int i=0;i

                 {

                     if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1)

                     {

                            //一个slot的状态比最好的playback状态更新(<100ms)

                            State rhs = m_BufferedState[Mathf.Max(i-1, 0)];

                            // The best playback state (closest to 100 ms old (default time))

                            // 最好的playback状态(最接近100ms时长(默认时间))

                            State lhs = m_BufferedState[i];                             

                            // 用两个slots的时间去测定插值是否是必要的。

                            double length = rhs.timestamp - lhs.timestamp;

                         float t = 0.0F;

                            // 时间差越接近于100毫秒t越接近于1,在这种情况下只用rhs。

                             //例如:

                            // Time是10.000,所以sampleTime是9.900

                            // lhs.time是9.910 rhs.time是9.980length是0.070

                            // t是9.900 - 9.910 / 0.070 = 0.14。所以它用了rhs的14%,lhs的86% 

                            if (length > 0.0001)

                           t = (float)((interpolationTime - lhs.timestamp) / length); 

                              // 如果 t=0 =>直接使用lhs

                        transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);

                     transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);

                     return;

                     }

                  }

              }

               //使用插值

               else

               {

                 State latest = m_BufferedState[0];    

                 float extrapolationLength = (float)(interpolationTime - latest.timestamp);

                   // 不为多于500毫秒的插值,你需要仔细的做。

                    if (extrapolationLength < m_ExtrapolationLimit)

                 {

                       float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg; Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity); 

                       rigidbody.position = latest.pos + latest.velocity * extrapolationLength;         

                       rigidbody.rotation = angularRotation * latest.rot; rigidbody.velocity = latest.velocity;

                       rigidbody.angularVelocity = latest.angularVelocity;

                  }

             }

        }

  } 

Unity3d多人在线教程_第64张图片在Hierarchy选择你的SpaceCraftFBX物体(记住不在Project面板里的那个游戏对象)

Unity3d多人在线教程_第65张图片添加NetworkRigidboddy.CS脚本到你的选择的物体上。

Unity3d多人在线教程_第66张图片关闭NetworkRigidbody组件(仅仅是在组件上打上个标记)。我们将在以后用一些条件下激活它。

Unity3d多人在线教程_第67张图片在Network View组件中:改变Observed把SpaceCraftFBX(Transform)换成SpaceCraftFBX(NetworkRigidbody)参数.

Unity3d多人在线教程_第68张图片创建一个新的JavaScript文件并命名为RigidAssign

Unity3d多人在线教程_第69张图片把RigidAssign.js脚本添加到你的物体上然后编辑这个文件:

function OnNetworkInstantiate (msg : NetworkMessageInfo) 

{

    if (networkView.isMine)

    {

        var _NetworkRigidbody : NetworkRigidbody = GetComponent("NetworkRigidbody");        

        _NetworkRigidbody.enabled = false;

    }

    else

    {

         name += "Remote";

        var _NetworkRigidbody2 :NetworkRigidbody = GetComponent("NetworkRigidbody");

        _NetworkRigidbody2.enabled = true;

     }

}

 

创建一个新的预制物体,给它命名:SpaceCraft。然后把它放到预制文件夹里。

Unity3d多人在线教程_第70张图片把你的SpaceCraftFBX从Hierarchy面板拖到Project面板里新的预制物体(SpaceCraft)。

Unity3d多人在线教程_第71张图片从场景中删除SpaceCraftFBX文件。

Unity3d多人在线教程_第72张图片为你的SpaceCraft预制物体创建“SpaceCraft”标签(tag)

Unity3d多人在线教程_第73张图片再选中你的SpaceCraft预置物体把标签指定给它:点击"Untagged"并且选择"SpaceCraft"

现在看像下面这个样子Unity3d多人在线教程_第74张图片

现在为Network准备了SpaceCraft!

Unity3d多人在线教程_第75张图片创建一个新的JS文件并且命名Instantialte.js

Unity3d多人在线教程_第76张图片把这个脚本文件拖给Hierarchy面板里的Spawn物体,然后编写以下代码:

var SpaceCraft : Transform;  

function OnNetworkLoadedLevel ()

{

  // Instantiating SpaceCraft when Network is loaded

  // 实例化SpaceCraft当网络加载完成的时候

  Network.Instantiate(SpaceCraft, transform.position, transform.rotation, 0);

}

function OnPlayerDisconnected (player : NetworkPlayer)

{

   // Removing player if Network is disconnected

   // 移除player,当网络断开连接。

   Debug.Log("Server destroying player");

   Network.RemoveRPCs(player, 0);

   Network.DestroyPlayerObjects(player);

Unity3d多人在线教程_第77张图片选择Spawn物体在Hierarchy并且设置Space Craft参数给SpaceCraft(Transform)(从列表选择你的SpaceCraft预设)

Unity3d多人在线教程_第78张图片从Hierachy面板里选择Main Camera物体。

Unity3d多人在线教程_第79张图片打开Smooth Follow脚本并且稍作修改:

var target : Transform;

var distance : float = 10.0;

var height : float = 5.0;

var heightDamping : float = 2.0;

var rotationDamping : float = 3.0;  

function LateUpdate ()

{

    if(GameObject.FindWithTag("SpaceCraft"))

    {

         if (!target)

             target = GameObject.FindWithTag("SpaceCraft").transform;  

              // 计算当前的选择角度

             var wantedRotationAngle : float = target.eulerAngles.y;

             var wantedHeight : float = target.position.y + height;

             var currentRotationAngle : float = transform.eulerAngles.y;

             var currentHeight : float = transform.position.y;

             // 减幅y轴方向的旋转。 

             var dt : float = Time.deltaTime;

             currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, rotationDamping * dt);

             //减幅高度

             currentHeight = Mathf.Lerp (currentHeight, wantedHeight, heightDamping * dt);

             //转换角度变成旋转 

             var currentRotation : Quaternion = Quaternion.Euler (0, currentRotationAngle, 0);

             // 设置cmera的位置在x-z平面成:

             // 与后面的target的距离多少米

             transform.position = target.position;

             var pos : Vector3 = target.position - currentRotation * Vector3.forward * distance;

             pos.y = currentHeight;

             // 设置摄像机的高度

             transform.position = pos;

             // 总是朝向目标

             transform.LookAt (target);

      }

}

 

Unity3d多人在线教程_第80张图片把Player Controls脚本拖到Main Camera上,然后编辑。

Unity3d多人在线教程_第81张图片把代码修改成这样的:

var turnSpeed : float = 3.0;

var maxTurnLean : float = 70.0;

var maxTilt : float = 50.0;  

var sensitivity : float = 0.5;  

var forwardForce : float = 5.0;

var guiSpeedElement : Transform;

var craft : GameObject;  

 

private var normalizedSpeed : float = 0.2;

private var euler : Vector3 = Vector3.zero;  

var horizontalOrientation : boolean = true;  

function Awake ()

{

    if (horizontalOrientation)

    {

       iPhoneSettings.screenOrientation = iPhoneScreenOrientation.LandscapeLeft;

    }

    else

    {

       iPhoneSettings.screenOrientation = iPhoneScreenOrientation.Portrait;

     } 

     guiSpeedElement = GameObject.Find("speed").transform;

     guiSpeedElement.position = new Vector3 (0, normalizedSpeed, 0);

  } 

function FixedUpdate ()

{

    if(GameObject.FindWithTag("SpaceCraft"))

    {

       GameObject.FindWithTag("SpaceCraft").rigidbody.AddRelativeForce(0, 0, normalizedSpeed * (forwardForce*3)); 

       var accelerator : Vector3 = iPhoneInput.acceleration;

       if (horizontalOrientation)

       {

           var t : float = accelerator.x;

           accelerator.x = -accelerator.y;

           accelerator.y = t;

       } 

       // 基于重力感应旋转

       euler.y += accelerator.x * turnSpeed;

        // Since we set absolute lean position, do some extra smoothing on it

       euler.z = Mathf.Lerp(euler.z, -accelerator.x * maxTurnLean, 0.2); 

        //由于我们的设置绝对依赖position,所以做了一些额外的平滑。

       euler.x = Mathf.Lerp(euler.x, accelerator.y * maxTilt, 0.2);

       // 应用旋转并且应用一些平滑

       var rot : Quaternion = Quaternion.Euler(euler);

       GameObject.FindWithTag("SpaceCraft").transform.rotation = Quaternion.Lerp (transform.rotation, rot, sensitivity);

     }

}

function Update ()

{

    for (var evt : iPhoneTouch in iPhoneInput.touches)

    {

        if (evt.phase == iPhoneTouchPhase.Moved)

        {

            normalizedSpeed = evt.position.y / Screen.height;

            guiSpeedElement.position = new Vector3 (0, normalizedSpeed, 0);

         }

      }

 }

Unity3d多人在线教程_第82张图片添加脚本Missile launcher脚本给Main Camera。设置Missile(导弹)参数为MissilePrefab。

Unity3d多人在线教程_第83张图片打开Missile Launcher然后编辑。

Unity3d多人在线教程_第84张图片我们添加一个计时器给shooting(射击)同时也作一些小小的调整。

var missile : GameObject;

var timer : int = 0; 

function FixedUpdate()

{

   timer++;

function Update ()

{

    if ((Input.GetMouseButtonDown (0))&&(timer>10))

    {

        // 如果SpaceCraft 存在的话

        if(GameObject.FindWithTag("SpaceCraft"))

        {

           var position : Vector3 = new Vector3(0, -0.2, 1) * 10.0;

           position = GameObject.FindWithTag("SpaceCraft").transform.TransformPoint(position);

           //实例化

           var thisMissile : GameObject = Network.Instantiate (missile, position, GameObject.FindWithTag("SpaceCraft").transform.rotation,0) as GameObject;

           Physics.IgnoreCollision(thisMissile.collider, GameObject.FindWithTag("SpaceCraft").collider); 

           timer = 0;

            }

       }

}

Unity3d多人在线教程_第85张图片创建新的标签“Missile”然后分配给missilePrefab预制物体。

Unity3d多人在线教程_第86张图片选择missilePrefab物体并且打开MissileTrajector脚本来编辑。

Unity3d多人在线教程_第87张图片我们将使它有能力击败其它的SpaceCraft

var explosion : GameObject;  

function OnCollisionEnter(collision : Collision)

{

    if(GameObject.FindWithTag("SpaceCraft"))

    {

        if(((collision.gameObject.tag == "Untagged")||(collision.gameObject.tag == "SpaceCraft"))&&(collision.gameObject.tag != "Missile"))

        {

             var contact : ContactPoint = collision.contacts[0];

             Instantiate (explosion, contact.point + (contact.normal * 5.0) , Quaternion.identity); 

              if (collision.gameObject.tag == "SpaceCraft")

              {

                 Instantiate (explosion, contact.point + (contact.normal * 5.0) , camera.main.transform.rotation);

                 collision.gameObject.transform.position = GameObject.Find("Spawn").transform.position;

               }

               Destroy (gameObject);

            }

      }

function FixedUpdate ()

{

     if(GameObject.FindWithTag("Missile"))

     {

         rigidbody.AddForce (transform.TransformDirection (Vector3.forward + Vector3(0,0.1,0)) * 720.0);

      }

 

你已经为网络准备好场景和物体了。

4。2。2。服务器/客户端。

现在是时候给这个游戏创建服务器了。我们将要创建三个不同的类型的servers(服务器)。

Unity3d多人在线教程_第88张图片创建一个新场景并保存为服务器。这个场景被用作服务器。

Unity3d多人在线教程_第89张图片创建一个新场景并保存为UDPServer。这个场景为用作UDP广播连接。

Unity3d多人在线教程_第90张图片创建一个新场景保存为MasterServer。这个场景被用作MasterServer。

Unity3d多人在线教程_第91张图片再创建一个新场景,保存为一个空场景。这个场景用作游戏断开时或启动一个新游戏之前清除掉所有的东西。

Unity3d多人在线教程_第92张图片把所有新建的场景到File->Building Settings->Add Open Scene. 你的ServerChoose应该在列表里是第一个例子。如:

提示:你可以创建一个文件夹把这些都放到一起。

 

Unity3d多人在线教程_第93张图片

 

Unity3d多人在线教程_第94张图片打开ServerChoose场景

Unity3d多人在线教程_第95张图片创建一个新的JS文件并命名它为Menu。我们将用这个场景及这个脚本来选择不同类型的服务器。

Unity3d多人在线教程_第96张图片把这个脚本添加给主摄像机(Main Camera)。

function OnGUI()

{

    GUI.Label(new Rect((Screen.width/2)-80,(Screen.height/2)-130,200,50),"SELECT CONNECTION TYPE");

    GUI.Label(new Rect((Screen.width-220),(Screen.height-30),220,30),"STAR-TROOPER MULTIPLAYER DEMO"); 

    if(GUI.Button(new Rect((Screen.width/2)-100,(Screen.height/ 2)-100,200,50),"Master Server Connection"))

    {

          Application.LoadLevel("MasterServer");

     } 

     if(GUI.Button(new Rect((Screen.width/2)-100,(Screen.height/2)-40,200,50),"Direct Connection"))

     {

          Application.LoadLevel("StarTrooper");

     } 

     if(GUI.Button(new Rect((Screen.width/2)-100,(Screen.height/2)+20,200,50),"UDP Connection"))

     {

          Application.LoadLevel("UDPServer");

     }

 }

Unity3d多人在线教程_第97张图片打开MasterServer场景

Unity3d多人在线教程_第98张图片创建一个新的JS文件并命名它NetworkLevelLoad。我们将用这个脚本把StarTrooper场景和物体加载网络中来。

Unity3d多人在线教程_第99张图片创建一个新的Empty GameObject并命名它ConnectionGUI。

Unity3d多人在线教程_第100张图片把NetworkLevelLoad脚本添加到ConnectionGUI游戏物体上

 

private var lastLevelPrefix = 0;

function Awake ()

{

    // 网络场景加载完成在一个分离的通道 

    DontDestroyOnLoad(this);

    networkView.group = 1;

    Application.LoadLevel("EmptyScene");

function OnGUI ()

{

    //当network运行(服务器和客户端)然后显示场景“StarTrooper”

    if (Network.peerType != NetworkPeerType.Disconnected)

    {

         if (GUI.Button(new Rect(350,10,100,30),"StarTrooper"))

         {

             //确信没有旧的RPC调用是在缓冲的然后发送加载的场景命令

             Network.RemoveRPCsInGroup(0);

             Network.RemoveRPCsInGroup(1);

             //加载场景用增加后的level prefix(为查看ID)

             networkView.RPC( "LoadLevel", RPCMode.AllBuffered, "StarTrooper", lastLevelPrefix + 1);

         }

     }

@RPC

function LoadLevel (level : String, levelPrefix : int)

{

      Debug.Log("Loading level " + level + " with prefix " + levelPrefix); lastLevelPrefix = levelPrefix;

      // 我们在网络上没有理由在默认的通道去发送更多的数据,因为我们将要加载场景,所有的这个物体将被删除。

      Network.SetSendingEnabled(0, false);  

      //我们需要停止接受因为首先场景必须被加载。

      //一旦场景加载完毕,RPC和其他状态的更新依附到场景中物体被允许去发射。

      Network.isMessageQueueRunning = false;  

      // 所有的newtork views从一个场景加载将得到一个prefix在他们NetworkViewID中。

      //这个将预防旧的来自于客户端的更新泄露进一个新创建的场景

      Network.SetLevelPrefix(levelPrefix);

      Application.LoadLevel(level); yield; yield;

      // Allow receiving data again允许再一次加载数据

      Network.isMessageQueueRunning = true;

      //现在场景已经加载完毕然后我们可以开始发送我们的数据。

      Network.SetSendingEnabled(0, true);  

      //通知我们的物体场景和网络已经准备好了。

      var go : Transform[] = FindObjectsOfType(Transform);

      var go_len = go.length;  

      for (var i=0;i

      {

          go[i].SendMessage("OnNetworkLoadedLevel",SendMessageOptions.DontRequireReceiver);

       }

function OnDisconnectedFromServer ()

{

      Application.LoadLevel("EmptyScene");

}  

@script RequireComponent(NetworkView)

// Ensure that this script has added a Netw

 

确信这个脚本已经自动添加了NetworkView,另外从Component菜单添加这个脚本workLevelLoad.

  创建一个新的JavaScript文件并且命名MasterServerGUI。用这个脚本我们将创建一个Master Server/Client,一些GUI将用到它。

把这个文件添加给ConnectionGUI物体并且打开这个脚本去编辑:

DontDestroyOnLoad(this);

  

var gameName = "YourGameName";

var serverPort = 25002;

private var timeoutHostList = 0.0;

private var lastHostListRequest = -1000.0;

private var hostListRefreshTimeout = 10.0;

private var natCapable : ConnectionTesterStatus = ConnectionTesterStatus.Undetermined;

private var filterNATHosts = false;

private var probingPublicIP = false;

private var doneTesting = false;

private var timer : float = 0.0; 

 

private var windowRect = Rect (Screen.width-300,0,300,100);

private var hideTest = false;

private var testMessage = "Undetermined NAT capabilities";  

 

// 如果没有运行客户端启用这个在服务器的机器上。

//MasterServer.dedicatedServer = true; 

 

function OnFailedToConnectToMasterServer(info: NetworkConnectionError)

{

     Debug.Log(info);

function OnFailedToConnect(info: NetworkConnectionError)

{

     Debug.Log(info);

function OnGUI ()

{

     ShowGUI();

}  

function Awake ()

{

     // 开始连接测试

     natCapable = Network.TestConnection(); 

     //  这个机器有什么种类的IPTestConnection也在测试结果中显示。

     if (Network.HavePublicAddress())

         Debug.Log("This machine has a public IP address");

     else

         Debug.Log("This machine has a private IP address");

   } 

function Update()

{

       // 如果测试不确定,保持运行

       if (!doneTesting)

        {

              TestConnection();

        }

function TestConnection()

{

     // 开始/轮询连接测试,在一个标签里面报告结果并且对结果做出相应的反应。

      natCapable = Network.TestConnection();

      switch (natCapable)

      {

          case

             ConnectionTesterStatus.Error:

             testMessage = "Problem determining NAT capabilities";

             doneTesting = true;

             break;  

          case

             ConnectionTesterStatus.Undetermined:

             testMessage = "Undetermined NAT capabilities";

             doneTesting = false;

             break;  

          case

             ConnectionTesterStatus.PrivateIPNoNATPunchthrough:

             testMessage = "Cannot do NAT punchthrough, filtering NAT enabled hosts for client connections," +" local LAN games only.";

             filterNATHosts = true; Network.useNat = true;

             doneTesting = true;

             break; 

         case

             ConnectionTesterStatus.PrivateIPHasNATPunchThrough:

         if (probingPublicIP)

             testMessage = "Non-connectable public IP address (port "+ serverPort +" blocked),"+" NAT punchthrough can circumvent the firewall.";

         else

             testMessage = "NAT punchthrough capable. Enabling NAT punchthrough functionality.";

              // NAT功能在服务器开始的时候是可用的,如果主机需要它在此基础上客户应该启用它,               Network.useNat = true; doneTesting = true;

               break; 

          case

               ConnectionTesterStatus.PublicIPIsConnectable:

               testMessage = "Directly connectable public IP address.";

               Network.useNat = false; doneTesting = true; break;  

               //这种情况是比较特殊,因为我们现在需要检查是否可以通过使用NAT穿通         case

               ConnectionTesterStatus.PublicIPPortBlocked:

               testMessage = "Non-connectble public IP address (port " + serverPort +" blocked),"+" running a server is impossible."; 

               Network.useNat = false;

               // 如果没有NAT穿透,测试将被搭建在这个公共的IP,强制一个测试

           if (!probingPublicIP)

           {

                Debug.Log("Testing if firewall can be circumnvented");

                natCapable = Network.TestConnectionNAT();

                probingPublicIP = true; timer = Time.time + 10;

           }

           //NAT穿透测试被执行但是我们仍然是阻塞的。 

          else if (Time.time > timer)

          {

                probingPublicIP = false;

                // reset

                Network.useNat = true;

                doneTesting = true;

           }

          break;

       case

             ConnectionTesterStatus.PublicIPNoServerStarted:

             testMessage = "Public IP address but server not initialized," +"it must be started to check server accessibility. Restart connection test when ready.";

             break;

        default: testMessage = "Error in test routine, got " + natCapable;

       }

 } 

function ShowGUI()

{

   if (GUI.Button (new Rect(100,10,120,30),"Retest connection"))

   {

         Debug.Log("Redoing connection test");

         probingPublicIP = false; doneTesting = false;

         natCapable = Network.TestConnection(true);

    } 

    if (Network.peerType == NetworkPeerType.Disconnected)

    {

         // 开始一个新的服务器

         if (GUI.Button(new Rect(10,10,90,30),"Start Server"))

         {

               Network.InitializeServer(32, serverPort); MasterServer.updateRate = 3;

               MasterServer.RegisterHost(gameName, "stuff", "profas chat test");

          } 

          // 刷新主机

          if (GUI.Button(new Rect(10,40,210,30),"Refresh available Servers") || Time.realtimeSinceStartup > lastHostListRequest + hostListRefreshTimeout)

          {

               MasterServer.ClearHostList(); MasterServer.RequestHostList (gameName);

               lastHostListRequest = Time.realtimeSinceStartup; Debug.Log("Refresh Click");

          } 

          var data : HostData[] = MasterServer.PollHostList();  

          var _cnt : int = 0;

          for (var element in data)

          {

               // Do not display NAT enabled games if we cannot do NAT punchthrough 

               //不显示NAT功能的游戏,如果我们不能这样做NAT穿透

               if ( !(filterNATHosts && element.useNat) )

               {

                    var name = element.gameName + " " + element.connectedPlayers + " / " + element.playerLimit;

                    var hostInfo; hostInfo = "[";

                    // Here we display all IP addresses, there can be multiple in cases where

                    // internal LAN connections are being attempted. In the GUI we could just display    connect to the  

                    // the first one in order not confuse the end user, but internally Unity will

                    // do a connection check on all IP addresses in the element.ip list, and  

                    // first valid one.

                    //在这个这里我们显示所有IP地址,可以有不同情况内部局域网连接被尝试。在GUI里,我们可以仅仅显示第一个为了不混淆终端用户,但是内部Unity将做一个连接检查在所有的IP地址上在element.ip列表里,并且连接到第一个有效的ip。

                    for (var host in element.ip)

                    {

                         hostInfo = hostInfo + host + ":" + element.port + " ";

                     } hostInfo = hostInfo + "]";  

                    if (GUI.Button(new Rect(20,(_cnt*50)+90,400,40),hostInfo.ToString()))

                    {

                         // Enable NAT functionality based on what the hosts if configured to do

                         //主机是否配置决定是否启用NAT功能。

                         Network.useNat = element.useNat;  

                         if (Network.useNat)

                              print("Using Nat punchthrough to connect");

                         else

                              print("Connecting directly to host"); 

                              Network.Connect(element.ip, element.port);

 

                     }

                }

            }

      }

      else

      {

            if (GUI.Button (new Rect(10,10,90,30),"Disconnect"))

            {

                   Network.Disconnect();

                   MasterServer.UnregisterHost();

             }

        }

  } 

 

现在你可以用Master Server来测试你的工程了

Unity3d多人在线教程_第101张图片去Edit -> Project Settings -> Player然后设置你的iPhone Bundle Identifier并且改变Default Screen Orientation 成 Landscape Left.

Unity3d多人在线教程_第102张图片编译你的工程到iPhone。

Unity3d多人在线教程_第103张图片运行Server场景在编辑器。

Unity3d多人在线教程_第104张图片点击在编辑器里面Master Server Connection和iPhone上。

Unity3d多人在线教程_第105张图片点击Start Server在编辑器。

Unity3d多人在线教程_第106张图片服务器应该应用在iPhone上(如果没有,点击刷新可用服务器)。

Unity3d多人在线教程_第107张图片连接到服务器从iPhone。

Unity3d多人在线教程_第108张图片点击StarTrooper 按钮在编辑器或者在iPhone上。

Unity3d多人在线教程_第109张图片你的图片应该看起来像这张图片:

Unity3d多人在线教程_第110张图片

我们已经完成了我们的Master Server

现在我们需要去创建一个第二种连接方式 – UDP广播连接:

Unity3d多人在线教程_第111张图片打开UDPServer

Unity3d多人在线教程_第112张图片创建一个新的C#文件并且命名他UDPConnectionGUI.

Unity3d多人在线教程_第113张图片把UDPConnection文件移到Plugins文件夹。

Unity3d多人在线教程_第114张图片创建一个新的空物体并且命名它UDPServer.

Unity3d多人在线教程_第115张图片指定UDPConnectionGUI文件到UDPServer 物体。(或者在你写了接下来的代码以后分配他)。

Unity3d多人在线教程_第116张图片分配一个NetworkView组件到UDPServer物体并且改变Observed参数给UDPServer(Transform)。

Unity3d多人在线教程_第117张图片分配NetworkLevelLoad脚本文件给UDPServer物体。

Unity3d多人在线教程_第118张图片打开UDPCOnnectionGUI然后写以下代码:

using UnityEngine;

using System.Collections;

using System.Net;

using System.Net.Sockets;

using System.Threading;

 

public class UDPConnectionGUI : MonoBehaviour

{

     private UdpClient server;

     private UdpClient client;

     private IPEndPoint receivePoint;

     private string port = "6767";

     private int listenPort = 25001;

     private string ip = "0.0.0.0";

     private string ip_broadcast = "255.255.255.255";

     private bool youServer = false;

     private bool connected = false;

     private string server_name = "";

     private int clear_list = 0;

     public void Update()

     {

          if(clear_list++>200)

          {

               server_name = "";

               clear_list = 0;

           }

     } 

     public void Start()

     {

           Debug.Log("Start"); LoadClient();

      }  

      public void LoadClient()

      {

           client = new UdpClient(System.Convert.ToInt32(port));

           receivePoint = new IPEndPoint(IPAddress.Parse(ip),System.Convert.ToInt32(port));

           Thread startClient = new Thread(new ThreadStart(start_client));

           startClient.Start();

       }  

       public void start_client()

       {

           bool continueLoop =true; 

           try 

           {

               while(continueLoop)

               {

                    byte[] recData = client.Receive(ref receivePoint);

                    System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding();

                    server_name = encode.GetString(recData); if(connected) { server_name = "";

                    client.Close();

                    break;

                }

           }

       }

       catch {}

    } 

    public void start_server()

    {

        try

        {

             while(true)

            {

                 System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding();  

                 byte[] sendData = encode.GetBytes(Network.player.ipAddress.ToString());

                 server.Send(sendData,sendData.Length,ip_broadcast,System.Convert.ToInt32(port));

                 Thread.Sleep(100);

             }

          }

          catch {}

      }  

      void OnGUI()

      {

          if(!youServer)

          {

              if(GUI.Button(new Rect(10,10,100,30),"Start Server"))

              {

                   youServer = true;

                   Network.InitializeServer(32, listenPort);

                   string ipaddress = Network.player.ipAddress.ToString();

                   ip = ipaddress;  

                   client.Close();

                   server = new UdpClient(System.Convert.ToInt32(port));

                   receivePoint = new IPEndPoint(IPAddress.Parse(ipaddress),System.Convert.ToInt32(port));

                   Thread startServer = new Thread(new ThreadStart(start_server));

                   startServer.Start();

             }  

             if(server_name!="")

             {

                 if(GUI.Button(new Rect(20,100,200,50),server_name))

                 {

                      connected = true; Network.Connect(server_name, listenPort);

                 }

             }

        }

        else

        {

              if(GUI.Button(new Rect(10,10,100,30),"Disconnect"))

              {

                  Network.Disconnect();

                  youServer = false; server.Close();

                  LoadClient();

               }

          }

     }

}

现在你可以测试这个连接类型:

Unity3d多人在线教程_第119张图片按上面的要求设置你的Player Settings(如果你刚刚没有改变它们的话)。

Unity3d多人在线教程_第120张图片把工程编译到iPhone。

Unity3d多人在线教程_第121张图片在编辑器播放UDPServer场景。

Unity3d多人在线教程_第122张图片从编辑器里面点击Start Server开启服务器。

Unity3d多人在线教程_第123张图片当IP address 按钮显示的时候从iPhone连接到服务器。

Unity3d多人在线教程_第124张图片你的场景应该看起来像这样:

Unity3d多人在线教程_第125张图片

我们已经完成我们的UDP广播服务器

 现在我们需要去创建一个第三种连接方式 – 直接连接:

Unity3d多人在线教程_第126张图片打开StarTrooper场景

Unity3d多人在线教程_第127张图片创建一个新的JavaScript文件并且命名它ConnectionGUI.

Unity3d多人在线教程_第128张图片指定ConnectionGUI.js文件给Main Camera并且添写以下代码:

var remoteIP = "127.0.0.1";

var remotePort = 25000;

var listenPort = 25000; 

var useNAT = false; 、

var yourIP = "";

var yourPort = "";

function Awake()

{

    if (FindObjectOfType(MasterServerGUI))

        this.enabled = false; 

    if(FindObjectOfType(UDPConnectionGUI))

        this.enabled = false;

}

function OnGUI ()

{

    if (Network.peerType == NetworkPeerType.Disconnected)

    {

       // If not connected如果没有连接

       if (GUI.Button (new Rect(10,10,100,30),"Connect"))

       {

           Network.useNat = useNAT;

           // Connecting to the server连接服务器

           Network.Connect(remoteIP, remotePort);

       }

       if (GUI.Button (new Rect(10,50,100,30),"Start Server"))

       {

          Network.useNat = useNAT;

          // Creating server创建服务器

          Network.InitializeServer(32, listenPort); 

         // Notify our objects that the level and the network is ready

         //通知我们场景里物体,network(网络)准备好了

         for (var go : GameObject in FindObjectsOfType(GameObject))

         {

             go.SendMessage("OnNetworkLoadedLevel", SendMessageOptions.DontRequireReceiver);

          }

      }

      remoteIP = GUI.TextField(new Rect(120,10,100,20),remoteIP);

      remotePort = parseInt(GUI.TextField(new Rect(230,10,40,20),remotePort.ToString()));

   }

   else

   {

       // If connected如果连接了

       // Getting your ip address and port获得你的ip地址和端口

       ipaddress = Network.player.ipAddress;

       port = Network.player.port.ToString();

       GUI.Label(new Rect(140,20,250,40),"IP Adress: "+ipaddress+":"+port);

       if (GUI.Button (new Rect(10,10,100,50),"Disconnect"))

       {

           // Disconnect from the server从服务器断开连接

           Network.Disconnect(200);

        }

    }

function OnConnectedToServer()

{

    // Notify our objects that the level and the network is ready 

    // 通知我们的物体场景和network准备好了

    for (var go : GameObject in FindObjectsOfType(GameObject))

       go.SendMessage("OnNetworkLoadedLevel", SendMessageOptions.DontRequireReceiver);

}  

function OnDisconnectedFromServer ()

{

     if (this.enabled != false)

         Application.LoadLevel(Application.loadedLevel);

     else

     {

         var _NetworkLevelLoad : NetworkLevelLoad = FindObjectOfType(NetworkLevelLoad);

         _NetworkLevelLoad.OnDisconnectedFromServer();

      }

 } 

现在可以测试这种连接方式了。

Unity3d多人在线教程_第129张图片按上面的要求设置你的Player Settings(如果你刚刚没有改变他们)

Unity3d多人在线教程_第130张图片 把工程编译到iPhone

Unity3d多人在线教程_第131张图片播放StarTrooper场景在编辑器。

Unity3d多人在线教程_第132张图片在编辑器和在iPhone上点击Direct Connection。

Unity3d多人在线教程_第133张图片从编辑器点击Start Server开启服务器。

Unity3d多人在线教程_第134张图片在iPhone上IP框输入IP地址的。你可以从Editor Player场景得到IP地址。

Unity3d多人在线教程_第135张图片你的场景应该看起来想这张图片。

Unity3d多人在线教程_第136张图片

Unity3d多人在线教程_第137张图片

我们已经完成我们的客户段和服务器通三种方式。。。。

 

4。3。3。最后润色

 

我们的工程现在已经基本完成。为了使我们的游戏更具可玩性我们需要去添加一些最终的润色:

Unity3d多人在线教程_第138张图片打开StarTrooper场景

Unity3d多人在线教程_第139张图片选择SpaceCraft预设并且设置Transform Scale参数成:1.8,1.8,1.8.

Unity3d多人在线教程_第140张图片 选择Main Camera在Hierarchy然后改变公共 变量在Play Controls脚本:Turn Speed: 3, Max Turn Lean: 70, Max Tilt: 50, Sensitivity: 0.5, Forward Force: 20.

Unity3d多人在线教程_第141张图片在Main Camera 里打开MissileLauncher.js脚本。在第16行,改变向量从Vector3(0,-0.2,1) 到Vector3(0,-0.3,0.5).

Unity3d多人在线教程_第142张图片选择missilePrefab预设并且设置Transform Scale参数成:8,8,8。

Unity3d多人在线教程_第143张图片你最后的屏幕应该看起来像这张图片。

Unity3d多人在线教程_第144张图片

提示:这些最新的修改在我的尝试中是基本的,所以可以随意的做出你的自己的修改和新的功能。

 

祝贺你!你已经完成了MultiPlayer MultiPlayer StarTrooper工程。。。。

你已经学习了如何去:

Unity3d多人在线教程_第145张图片准备一个场景和物体为network.

Unity3d多人在线教程_第146张图片变换一个场景和物体在network.

Unity3d多人在线教程_第147张图片一个单人游戏转换成多人

Unity3d多人在线教程_第148张图片创建一个Master Server/Client.

Unity3d多人在线教程_第149张图片创建一个Server/Client用直接连接。

Unity3d多人在线教程_第150张图片创建一个Server/Client用UDP广播连接。

Unity3d多人在线教程_第151张图片用主要的Network组件。

Unity3d多人在线教程_第152张图片用增加的Unity组件。


你可能感兴趣的:(unity3d)