---恢复内容开始---

用户手册,范例精讲。

用户手册上给出了一个简单的范例,并指出可以以此为基础进行相开发,再次对范例进行精讲。(NetworkManager对使用unity的轻量级游戏开发有很大帮助,大大解决了开发时间,但是对于吃鸡、农药等游戏估计其整个消息传递机制还是自定义的(此处为猜测))。不做赘述,立马开始(完整脚本均在文末):

1)挂在NetworkManager

如果用过很多插件的,会知道,在使用一个插件之前肯定要挂载一个manager或者controller,NetworkManager也一样,需要关在NetworkManager进行组建数据初始化等。所以新建游戏物体,并添加NetworkManager组件并勾选Dont Destroy on Load。参数中除了network address和port以外,需要对Spawn Info进行一下简单解释。Spawn表示网路范围内产生的游戏物体(根据字面意思以及unity测试自己定义的),比如吃鸡游戏,自己控制人物在别人的场景中也存在,这就是一个Spawn。Player prefab玩家预制,即游戏玩家控制的角色,如果是聊天软件则可以是聊天窗口。RegisteredSpawnablePrefabs表示对需要对网络范围内产生的游戏物体进行注册,如子弹,你打出的子弹需要对方也可以收到,就需要在网络范围内(即所有玩游戏的人场景)产生,他跟Player prefab玩家预制本质上是一样的。

2)挂在NetworkManagerHUD

在NetworkManager所在的游戏物体挂在此组件,此组件为unity自定义的简单UI,运行时会有四个选项:

LANHost:局域网范围内,本机为主机(服务端),同时本机也为客户端。

LANServer:局域网范围内,本机只为主机。

LANClient:局域网范围内,本机为客户端。

Enable。。:(用户手册解释为网络端(外网),并未进行相关测试。点进去就可以进行相关外网连接设置)括号内的解释不严谨,会在下一篇中详细解释

在此说明一下:LANHost与LANServer区别在于本身是否存在一个客户端,这个对于玩儿过CS1.5/1.6的人来说好理解,因为局域网需要一个人见主机,其他人加入,同时它本身也要参与游戏,目前的FPS游戏均为局域网范围外,所以本身只是客户端。

3)玩家生成

添加一个3D游戏物体capsule,并生成预制体,作为一个间的游戏玩家。并拖到NetWorkManager中的PlayerPrefab槽中。对此游戏物体添加NetworkIdentity组件,此组件用来标识游戏玩家身份,只要是场景中每个客户端在交互中实时生成的并可以被广泛看到的均需要此组件。此组件有两个参数,一个人ServerOnly一个是LocalPlayer权限,表示此游戏物是本地有控制权限还是服务端有控制权限(因为服务端和客户端会均存在一个相同的游戏物体,理论上都可以进行控制,所以要对此进行一下区分,后续会讲到怎么处理)。如果需要实时更新位置信息的,则还需要NetWorkTransform组件,来试试更新位置。

4)添加脚本PlayerMove(控制移动开火等)

完整脚本在最后端。

直接添加脚本并在脚本中添加如下代码

        float x = Input.GetAxis("Horizontal") * moveSpeed;
float z = Input.GetAxis("Vertical") * moveSpeed;
//transform.Translate(new Vector3(x, 0, z));
GetComponent<CharacterController>().Move(new Vector3(x, , z));
注:原程序用的transform.translate,本文通过添加角色控制器来移动

打包一个程序,运行选择客户端,editor运行选择LanHost。会发现两个场景中均存在两个游戏物体,移动游戏物体则两个游戏物体均移动。而我们需要的是只移动本客户端控制的游戏物体。所以需要作如下修改:

把MonoBehaviour换成NetworkBehaviour(后者继承在前者),然后通过NetworkBehaviour中的isLocalPlayer参数来来判断是否为本地运行,此部分代码如下:

  private void Update()
{
if (isLocalPlayer)
{
PlayerMove();
}
} private void PlayerMove()
{
float x = Input.GetAxis("Horizontal") * moveSpeed;
float z = Input.GetAxis("Vertical") * moveSpeed;
//transform.Translate(new Vector3(x, 0, z));
GetComponent<CharacterController>().Move(new Vector3(x, , z));
}

重新运行即为我们想要的效果。

下面为添加子弹:

新建一个球形游戏物体,调整一下初始位置和大小,添加NetworkIdentity和NetWorkTransform组件,并生成预制体,把此预制体拖动到NetworkManager中的RegisteredSpawnablePrefabs中进行注册。当按下F键时表示开火,在update中添加如下代码

     if (Input.GetKeyDown(KeyCode.F))
            {
                GameObject playerBullet = Instantiate(bullet, (*transform.forward + transform.position), Quaternion.identity);
playerBullet.GetComponent<Rigidbody>().velocity = playerBullet.transform.forward * bulletSpeed;
            }

运行测试会发现,客户端的两个游戏物体均发射子弹,而且服务端无子弹生成,若增加第二个客户端,则第二个客户端也不会生成子弹。代码放在isLocalPlayer触发的模块下可解决均发射子弹的问题。第二个问题则是client产生游戏物体不会在服务端产生也不会在其他client端产生。如果自己编写消息机制,则需要发送消息告诉服务端和其他client,产生子弹,但是

但是Unet为我们提供好了解决方案,即通过[Command](表示此方法客户端调用,服务端执行,对应的方法开头为Cmd;对应的[ClientRpc]表示服务端调用,客户端执行)让服务端去执行发射子弹命令,并通过NetworkServer.Spawn方法在每个客户端生成

代码如下:

        [Command]
    private void CmdFire()
    {
        GameObject playerBullet = Instantiate(bullet, (4*transform.forward + transform.position), Quaternion.identity);
        playerBullet.GetComponent<Rigidbody>().velocity = playerBullet.transform.forward * bulletSpeed;
        NetworkServer.Spawn(playerBullet);
        Destroy(playerBullet, 2f);       
    }

5)添加血量控制

在Player预制上添加MyCombat脚本以及HealthBar脚本(显示血量)。其中MyCombat中包含两个公共变量maxHealth最大生命值,currentHealth当前生命值。

HealthBar通过最大生命值和当前生命值通过GUI来显示相关数值。HealthBar见本文最后。MyCombat脚本需要一个公共方法,当受到子弹碰撞时减少血量,并当减少到一定数值是死亡或者重新生成,方法如下

    public void DecreaseHealth(float delta)
{
currentHealth -= delta; if(currentHealth <= ) { currentHealth = ; } if(currentHealth==)
{
if (isEnemy)
{
Destroy(gameObject);
}
else
{
RpcReset();
currentHealth = maxHealth;
}
}
}

同时我们还需要给子弹添加碰撞体,并添加碰撞事件,完整代码如下:

using UnityEngine;

public class MyBullet : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
MyCombat combat = collision.collider.gameObject.GetComponent<MyCombat>();
Destroy(gameObject); if(combat != null)
{
combat.DecreaseHealth();
}
}
}

如果我们需要所有数据均在服务端执行则需要如下处理。把MyCombat继承的MonoBehaviour改为NetworkBehaviour,并让DecreaseHealth方法在服务端执行,通过[SyncVar](同步变量,即把服务端的变量值同步到各个客户端)把当前血量同步到各个客户端,同时当期血量低于0时,让player回到初始位置,代码如下

   public void DecreaseHealth(float delta)
{
if (!isServer) return; currentHealth -= delta; if(currentHealth <= ) { currentHealth = ; } if(currentHealth==)
{
if (isEnemy)
{
Destroy(gameObject);
}
else
{
RpcReset();
currentHealth = maxHealth;
}
}
}     [ClientRpc]//服务端调用,client执行,运动(位置)权限在本地,服务端更改位置无法同步到每个客户端,只有本地更改才可以
    private void RpcReset()
    {
        transform.position = new Vector3(0, 0, 0);
    }

6)其他

官方例子中还给出了生成初始敌人的代码,以及客户端控制的Player更改颜色的方法。这两种处理比较简单,NetworkBehaviour中的方法,前者在服务端启动服务时调用,后者在启动游戏玩家时调用,代码分别如下

using UnityEngine;
using UnityEngine.Networking; public class MyEnemySpawner : NetworkBehaviour {
public GameObject enemy;
public int enemyCount = ; public override void OnStartServer()
{
for (int i = ; i < enemyCount; i++)
{
GameObject en = Instantiate(enemy);
en.transform.position = new Vector3(Random.Range(-, ), , Random.Range(-, ));
NetworkServer.Spawn(en);
}
}
}
    public override void OnStartLocalPlayer()
{
GetComponent<MeshRenderer>().material.color = Color.red;
}

完整代码:

using UnityEngine.Networking;
using UnityEngine; public class MyPlayerMove : NetworkBehaviour {
public float moveSpeed = 2f;
public float bulletSpeed = 50f;
public GameObject bullet; public override void OnStartLocalPlayer()
{
GetComponent<MeshRenderer>().material.color = Color.red;
} private void Update()
{
if (isLocalPlayer)
{
PlayerMove(); if (Input.GetKeyDown(KeyCode.F))
{
CmdFire();
}
}
} private void PlayerMove()
{
float x = Input.GetAxis("Horizontal") * moveSpeed;
float z = Input.GetAxis("Vertical") * moveSpeed;
//transform.Translate(new Vector3(x, 0, z));
GetComponent<CharacterController>().Move(new Vector3(x, , z));
} [Command]
private void CmdFire()
{
GameObject playerBullet = Instantiate(bullet, (*transform.forward + transform.position), Quaternion.identity);
playerBullet.GetComponent<Rigidbody>().velocity = playerBullet.transform.forward * bulletSpeed;
NetworkServer.Spawn(playerBullet);
Destroy(playerBullet, 2f);
}
}
using UnityEngine;
using UnityEngine.Networking; public class MyCombat : NetworkBehaviour {
public bool isEnemy = false; [SyncVar]
public float currentHealth = maxHealth; public static float maxHealth = ; public void DecreaseHealth(float delta)
{
if (!isServer) return; currentHealth -= delta; if(currentHealth <= ) { currentHealth = ; } if(currentHealth==)
{
if (isEnemy)
{
Destroy(gameObject);
}
else
{
RpcReset();
currentHealth = maxHealth;
}
}
} [ClientRpc]
private void RpcReset()
{
transform.position = new Vector3(, , );
}
}
using UnityEngine;

public class HealthBar : MonoBehaviour
{
GUIStyle healthStyle;
GUIStyle backStyle;
MyCombat combat; void Awake()
{
combat = GetComponent<MyCombat>();
} void OnGUI()
{
InitStyles(); // Draw a Health Bar
// 绘制一个血条 Vector3 pos = Camera.main.WorldToScreenPoint(transform.position); // draw health bar background
// 绘制血条背景
GUI.color = Color.grey;
GUI.backgroundColor = Color.grey;
GUI.Box(new Rect(pos.x - , Screen.height - pos.y + , Combat.maxHealth / , ), ".", backStyle); // draw health bar amount
// 绘制当前血量
GUI.color = Color.green;
GUI.backgroundColor = Color.green;
GUI.Box(new Rect(pos.x - , Screen.height - pos.y + , combat.currentHealth / , ), ".", healthStyle);
} void InitStyles()
{
if (healthStyle == null)
{
healthStyle = new GUIStyle(GUI.skin.box);
healthStyle.normal.background = MakeTex(, , new Color(0f, 1f, 0f, 1.0f));
} if (backStyle == null)
{
backStyle = new GUIStyle(GUI.skin.box);
backStyle.normal.background = MakeTex(, , new Color(0f, 0f, 0f, 1.0f));
}
} Texture2D MakeTex(int width, int height, Color col)
{
Color[] pix = new Color[width * height];
for (int i = ; i < pix.Length; ++i)
{
pix[i] = col;
}
Texture2D result = new Texture2D(width, height);
result.SetPixels(pix);
result.Apply();
return result;
}
}
using UnityEngine;

public class MyBullet : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
MyCombat combat = collision.collider.gameObject.GetComponent<MyCombat>();
Destroy(gameObject); if(combat != null)
{
combat.DecreaseHealth();
}
}
}
using UnityEngine;
using UnityEngine.Networking; public class MyEnemySpawner : NetworkBehaviour {
public GameObject enemy;
public int enemyCount = ; public override void OnStartServer()
{
for (int i = ; i < enemyCount; i++)
{
GameObject en = Instantiate(enemy);
en.transform.position = new Vector3(Random.Range(-, ), , Random.Range(-, ));
NetworkServer.Spawn(en);
}
}
}
 

---恢复内容结束---

NetworkManager网络通讯_Example(一)的更多相关文章

  1. NetworkManager网络通讯_NetworkManager(二)

    本文主要来实现一下自定UI(实现HUD的功能),并对Network Manger进行深入的讲解. 1)自定义manager 创建脚本CustomerUnetManger,并继承自NetworkMang ...

  2. NetworkManager网络通讯_破产版NetworkManager(五)

    根据对NetWorkServer 以及NetworkClient的理解,编写一个简易版的NetWork Manager.惯例全部代码放在最后 (一)NetWorkServer与NetworkClien ...

  3. NetworkManager网络通讯_问题汇总(四)

    此篇来填坑,有些坑是unet自身问题,而大部分则是理解不准确造成的(或者unity定义太复杂) 问题一: isLocalPlayer 值一直是false 出现场景:NetworkLobbyPlayer ...

  4. NetworkManager网络通讯_NetworkLobbyManager(三)

    此部分可以先建立游戏大厅,然后进入游戏,此处坑甚多耗费大量时间.国内百度出来的基本没靠谱的,一些专栏作家大V也不过是基本翻译了一下用户手册(坑啊),只能通过看youtube视频以及不停的翻阅用户手册解 ...

  5. NetworkManager网络通讯_networkReader/Writer(六)

    unet客户端和服务端进行消息发送时可以采用上一节中方法,也可以直接用networkReader/Writer类进行发送 (一)服务端/客户端注册消息 ; m_Server.RegisterHandl ...

  6. dicom网络通讯入门(3)

    接下来可以进行消息传递了 ,也就是dimse ,再来复习下 什么是dimse .n-set  n-create c-echo 这些都是dimse  他们都是属于一种结构的pdu 那就是tf-pdu(传 ...

  7. dicom网络通讯入门(2)

    第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了. 首先我们今天要干的事是实现一个echo响应测试工具 也就是echo 的scu,不是实现打印作业管理么.同学我告诉你还早着呢. ...

  8. 《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  9. Windows 网络通讯开发

    Windows 网络通讯开发 一.Windows网络开发API 由于C++标准库中没有网络库,所以进行网络开发的时候要调用系统API.Windows通讯开发API包括以下几个基本函数及成员类型: 1. ...

随机推荐

  1. Emacs 笔记二

    Emacs 笔记二 Table of Contents 1. 前言 2. emacs基本操作(常用快捷键) 3. emacs模式讲解 4. emacs缓冲区 5. org mode 5.1. 列表 5 ...

  2. Ubuntu server16.04安装配置驱动418.87、cuda10.1、cudnn7.6.4.38、anaconda、pytorch超详细解决

    目录 安装GCC 安装NVIDIA驱动 1. 卸载原有驱动(没装跳过) 2. 禁用nouveau 3. 安装NVIDIA显卡驱动 安装CUDA10.1 安装cudnn 安装anaconda 安装ten ...

  3. mybatis批量插入应用

    一.foreach简单介绍 foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合. foreach元素的属性主要有item,index,collection,open,sepa ...

  4. gitbook 入门教程之从零到壹发布自己的插件

    什么是插件 Gitbook 插件是扩展 Gitbook 功能的最佳方式,如果 Gitbook 没有想要的功能或者说网络上也没有现成的解决方案时,那么只剩下自食其力这条道路,让我们一起来自力更生开发插件 ...

  5. scalikejdbc 学习笔记(4)

    Batch 操作 import scalikejdbc._ import scalikejdbc.config._ object BatchOperation { def main(args: Arr ...

  6. Java基础学习笔记(六) - 数据结构和集合

    一.认识数据结构 1.数据结构有什么用? 合理的使用数据结构,可以更方便的查找存储数据. 2.常见的数据结构 数据存储常用结构有:栈.队列.数组.链表和红黑树. 栈:堆栈(stack),它是运算受限的 ...

  7. Activity初学乍练

    1.Activity的概念与Activity的生命周期图: 注意事项: onPause()和onStop()被调用的前提是: 打开了一个新的Activity!而前者是旧Activity还可见的状态:后 ...

  8. vue使用vant-ui实现上拉加载、下拉刷新和返回顶部

    vue使用vant-ui实现上拉加载.下拉刷新和返回顶部 vue现在在移动端常用的ui库有vant-ui和mint-ui,上拉加载.下拉刷新和返回顶部也是移动端最基础最常见的功能.下面就用vant-u ...

  9. mobaxterm和CRT的文件上传

    版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/submarineas/article/de ...

  10. node的重点学习笔记(1)————node

    node的重点学习笔记(1)----node 提到node就必须提一下他的npm了,npm是世界上最大的开放源代码的生态系统.通俗来说这就如同亚马逊丛林,要啥物种有啥物种,一个巨大的生态圈,里面有一堆 ...