欢迎光临散文网 会员登陆 & 注册

Unity学习笔记09联网

2021-11-30 08:14 作者:挽秋_z  | 我要投稿

联网

网络基础

多为策略游戏:MMORTS      角色扮演游戏:MMORPG

联网功能

1.现在的游戏引擎都提供了基本的联网功能,比如LAN方式的联网对战。

2.大规模多人在线游戏的网络处理很复杂。

局域网

一般没有专用的服务器,通常是一台机器充当两个角色,其他机器连接到该机器上,构成局域网,稳定性

比较高。

广域网

一般有一台专用的,性能极高的作为服务器,其他所有的机器通过互联网远程连接到该机器上。

游戏联网的网络架构(广域网下):一般是这两种




分组交换技术

互联网是一种分组交换并且容错的网络系统

1.分组交换(信息被分解为多个小的包(几个字节或千字节的大小)分别发送出去。

2.容错(数据包在网络出现错误或者服务器发生故障的情况下仍然可以发送,如果一个服务器出现故障,数据包将

使用其他路径到达目的地)

互联网的协议

可以分为3层

1,最底层的是IP协议(用于报文交换网络的一种面向数据的协议,定义了数据包在网际传送的格式)

2,UDP协议或者TCP协议。

UDP或者TCP层将大的数据包传给IP,IP将数据包分割为小的子数据包,为每个数据包加上一个信封再发送出去


3,最顶层的是一些应用层协议。

推荐书籍:


联网功能实现:使用游戏引擎自带的联网模块或者使用底层网络接口自己实现功能


玩家连线(联网)

游戏联网需要的几个概念

1.网络管理器(管理联网物体)

2.联网用户界面

3.具备联网功能的玩家预制件

4.具有联网能力的脚本和游戏物体

客户端,服务器

客户端:连接到服务器的游戏实例

服务器:专用服务器(广域网),主机服务器(局域网)

简单实践

在unity的Package Manager中下载安装Multiplayer   HLAPI。

新建一个空物体,为其添加NetworkManager和NetworkManagerHUD组件。

制作用来联网的预制件,为其添加NetworkIdentity(需要将Local Player Authority点上,组件是用来识别联网的物

体)组件和NetworkTransfrom(可以自动的将物体的变换在网络之间进行同步传输)。还添加Network Animator

组件(可以自动的将物体的动画在网络之间进行同步传输)。但需要一个动画控制器,也可以直接将预制件拖拽过

去,勾选所需的属性即可。

修改脚本的联网功能,将预制件拖拽给NetworkManager的Player Prefab。

运行游戏后,点击LAN Host(H)生成一个本地主机,要联网的话至少需要两个机器来联网,可以将当前场景再生成

一个副本(file--build and run,需要为其选择文件存储位置),然后运行两个游戏,一个作为服务器,一个作为客

户端,生成角色后可以在对方的场景里看到。

玩家角色比较特殊, 每个玩家控制自己本地的实例,然后由服务器端来完成同步


游戏物体同步

网络权限Network  Authority

1.客户端和服务器都可以控制游戏物体行为

2.网络权限指的是游戏物体如何以及在哪里被管理

3.默认是服务器端(玩家角色特殊,是在客户端)

动态生成游戏物体

1.非联网物体使用GameObject.Instantiate生成物体(动态生成游戏物体:先制作好预制件,然后使用这个函数)

2.而联网物体需要由服务器端来生成(使用spawn方式生成,生成的物体交由Network Manager管理)

联网和非联网游戏物体

联网游戏的场景中包含联网和非联网物体

1.联网的物体需要在全网同步。

2.非联网的物体只运行于本地(静态物体,比如石块、楼梯。不需要同步的物体,比如随风摆动的树木、草丛或者

云朵)。

联网物体

1.需要包含Network ldentity组件。

2.还需要开发人员指定物体的哪些属性需要同步。

3.哪些属性需要同步取决于游戏内容(对于玩家或者非玩家角色的位置、朝向。带有动画物体的动画状态。—些数

值,比如游戏剩余时长、玩家的能量值)

属性同步

1.可以自动同步的属性

Network Transform组件来同步物体变换属性。Network Animator组件来同步动画属性。

2.无法自动同步的属性(在脚本里面指定)

简单实践

Network transfrom中的Network send rate 调整为零,代表刚开始时自动同步,之后则通过脚本来控制同步。如

果完全自动同步的话对网络的要求很高。

新建一个子弹预制件拖拽给网络管理器的Registered  Spawnable  Prefabs(代表可网络生成的物体)

修改玩家控制脚本,让角色可以发射子弹,修改后记得将预制件赋给该脚本

using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.Networking;

namespace UnityStandardAssets.Characters.ThirdPerson
{
   [RequireComponent(typeof(ThirdPersonCharacter))]
   public class ThirdPersonUserControl : NetworkBehaviour
   {
       private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
       private Transform m_Cam;                  // A reference to the main camera in the scenes transform
       private Vector3 m_CamForward;             // The current forward direction of the camera
       private Vector3 m_Move;
       private bool m_Jump;                      // the world-relative desired move direction, calculated from the camForward and user input.

       //发射子弹
       public GameObject bulletPrefab;

       private void Start()
       {
           // get the transform of the main camera
           if (Camera.main != null)
           {
               m_Cam = Camera.main.transform;
           }
           else
           {
               Debug.LogWarning(
                   "Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
               // we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
           }

           // get the third person character ( this should never be null due to require component )
           m_Character = GetComponent<ThirdPersonCharacter>();
       }


       private void Update()
       {
           if (!isLocalPlayer)//如果不是本地要控制的玩家角色的话,就不进行后面的处理
           {
               return;
           }
           if (!m_Jump)
           {
               m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
           }
       }


       // Fixed update is called in sync with physics
       private void FixedUpdate()
       {
           if (!isLocalPlayer)//如果不是本地要控制的玩家角色的话,就不进行后面的处理
           {
               return;
           }
           // read inputs
           float h = CrossPlatformInputManager.GetAxis("Horizontal");
           float v = CrossPlatformInputManager.GetAxis("Vertical");
           bool crouch = Input.GetKey(KeyCode.C);

           // calculate move direction to pass to character
           if (m_Cam != null)
           {
               // calculate camera relative direction to move:
               m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
               m_Move = v * m_CamForward + h * m_Cam.right;
           }
           else
           {
               // we use world-relative directions in the case of no main camera
               m_Move = v * Vector3.forward + h * Vector3.right;
           }
#if !MOBILE_INPUT
           // walk speed multiplier
           if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif

           // pass all parameters to the character control script
           m_Character.Move(m_Move, crouch, m_Jump);
           m_Jump = false;

           //
           if (Input.GetMouseButtonDown(0))
           {
               CmdFire();
           }
       }
       public void Fire()
       {
           var bullet = Instantiate(bulletPrefab, transform.position + transform.up + transform.forward * 2,Quaternion.identity) as GameObject;
       }

       [Command]//加上这个前缀代表下面的函数是由客户端发起调用,但是在服务器端执行
       public void CmdFire()//必须加Cmd前缀,,,子弹联网
       {
           var bullet = Instantiate(bulletPrefab, transform.position + transform.up + transform.forward * 2, transform.rotation) as GameObject;
           NetworkServer.Spawn(bullet);//生成并同步到各客户端
       }
   }
}

玩家生命值脚本,赋给玩家预制件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class Health : NetworkBehaviour//有关网络的操作需要继承该类
{
   private static int hp_max = 100;//最大生命值
   public int hp = hp_max;//初始生命值
   public void GetDamage(int dmg)
   {
       if(!isServer)
       {
           return;
       }
       hp -= dmg;
       if(hp<0)
       {
           RpcRespawn();//生命值小于0时重置玩家
       }
   }
   [ClientRpc]//表示下面的函数是由服务器发起调用的,但是在客户端来执行
   public void RpcRespawn()//必须加上Rpc前缀
   {
       if(isLocalPlayer)
       {
           transform.position = Vector3.zero;//将位置重置为原点
           hp = hp_max;
       }
   }
   // Start is called before the first frame update
   void Start()
   {
       
   }

   // Update is called once per frame
   void Update()
   {
       
   }
}

子弹脚本,赋给子弹预制件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NetBullet : MonoBehaviour
{
   // Start is called before the first frame update
   void Start()
   {
       
   }

   // Update is called once per frame
   void Update()
   {
       
   }
   private void OnTriggerEnter(Collider other)//子弹预制件的Is trigger要点上
   {
       other.SendMessage("GetDamage",60,SendMessageOptions.DontRequireReceiver);//如果碰到的物体身上有GetDamage的话,执行健康值减少60的函数,因为有可能打到其他的物体
   }
}


角色同步

(非玩家角色,每一个客户端并不具备直接控制这些角色的能力,非玩家角色直接运行于服务器端,只有服务器端

才能对其进行控制以及网络同步)

简单实践

建立敌人预制件和脚本。

Instantiate

使用预制件在场景中生成物体,共有三个参数,一是预制件,二是生成物体的位置,三是生成物体的朝向。

示例:

var enemy = Instantiate(enemyProfab, new Vector3(unit_pos.x, 0, unit_pos.y), rnd_rot);

生成敌人的脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class EnemySpawner : NetworkBehaviour
{
   public GameObject enemyProfab;//敌人预制件
   public int enemyNum = 5;//生成的敌人数量

   public override void OnStartServer()//服务器端开始运行的时候生成敌人
   {
       for(int i=0;i<enemyNum;++i)
       {
           var unit_pos = Random.insideUnitCircle;//随机生成位置,使用这个函数是生成在一个单位圆内,unit_pos代表一个二维向量类型
           unit_pos = unit_pos * 6;//表示单位为6
           var rnd_rot = Quaternion.Euler(new Vector3(0, Random.Range(-180, 180), 0));//生成一个随机的朝向
           var enemy = Instantiate(enemyProfab, new Vector3(unit_pos.x, 0, unit_pos.y), rnd_rot);
           NetworkServer.Spawn(enemy);//由服务器端生成敌人
       }
   }
}

生成后要将敌人预制件拖拽上去。

NetworkIdentity中的Server  Only 如果勾选则表示阻止客户端有权利生成公用的敌人。

修改Health脚本,让子弹可以打到敌人,并且需要将敌人预制件的IsEnemy点上,表示是敌人,不是玩家

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class Health : NetworkBehaviour//有关网络的操作需要继承该类
{
   private static int hp_max = 100;//最大生命值
   public int hp = hp_max;//初始生命值

   public bool isEnemy = false;//标识角色是不是敌人
   public void GetDamage(int dmg)
   {
       if(!isServer)
       {
           return;
       }
       hp -= dmg;
       if(hp<0)
       {
           if (isEnemy) Destroy(gameObject);//如果敌人血量小于0让其消失
           RpcRespawn();//生命值小于0时重置玩家
       }
   }
   [ClientRpc]//表示下面的函数是由服务器发起调用的,但是在客户端来执行
   public void RpcRespawn()//必须加上Rpc前缀
   {
       if(isLocalPlayer)
       {
           transform.position = Vector3.zero;//将位置重置为原点
           hp = hp_max;
       }
   }
   // Start is called before the first frame update
   void Start()
   {
       
   }

   // Update is called once per frame
   void Update()
   {
       
   }
}













Unity学习笔记09联网的评论 (共 条)

分享到微博请遵守国家法律