第二部分——主角的创建

*
学习前说明:

项目源码:链接:https://pan.baidu.com/s/1g78L9QODXdRjoVcm-odRSg 密码:0pzo

源码引用自Siki老师的Unet基础系列教程,文章主要以解释为主,后期会添加一些Siki老师源码以外的新东西,敬请期待。

文中用红色显示的内容为我自己命名的关键名词,例如场景名、代码名、阐述代码或方法所实现的功能 等等

文中用蓝色显示的内容为UI元素、组件名或是变量名,例如Button、InputField、NetworkManager 等等

文中用紫色显示的内容为当前所解释的代码部分。例如:

            这里使用一个Console命令完成输出语句

public void ShowMessage()

{

     Console.WriteLine("Hello World.");

}

本文中仅讨论Unet用于局域网的情况

如有互联网联网需求,请自行查询Unity与Socket协议结合相关的文章,不推荐使用Unet完成

本文主要服务于渤海大学交互式虚拟现实开发基地,为求尽可能的细致和易懂,可能有些地方写的过于冗杂,不喜勿喷

*
需求分析

1.W/S键能够完成向前/向后的移动

2.左右键能够完成朝向的旋转

3.空格键能够完成开枪的操作,每发子弹造成10点的伤害

4.头上显示血量,每个人基础血量为100

*
常用内容(void)

1.继承自NetworkBehaviour而非MonoBehaviour

2.初始化放在OnStartLocalPlayer中而不是Start中

3.Update中放入 if(isLocalPlayer==false) return; 检查是不是本机

4.[SyncVar]修饰的变量,一般用于可以和服务端或是其他客户端交互发生变化的变量。强调具有影响性
。比如我的生命值(可被敌人伤害减少),我的攻击力(可以影响伤害敌人的效果),我的级别(可以影响我的伤害值、体型)等等

5.[SyncVar=hook"方法名"]修饰的变量,用于在变量变化后调用的方法。强调依赖性。比如升级时弹出技能升级面板、血量为0后屏幕变灰等等

6.[Command]修饰的方法,一般用于客户端所能操控的物体。强调主动性。比如我主动开枪,我主动移动,我主动生成随从等等

7.[ClientRpc]修饰的方法,一般用于客户端受到的效果。强调被动性
。比我我被敌人命中,我被杀死,我重生(被恢复满状态并被移动到指定位置)等等。一般配合 if(isServer==false)return; 使用

8.NetworkServer.Spawn()
方法,一般用于生成附属于自己的物体。比如小兵(可以帮自己打敌人),子弹(造成伤害给自己加经验)、棋子(能够判断自己胜利)等等

9.NetworkTransform
组件,一般用于当前控制角色的位移,比如主角的移动,汽车的移动。但是仅能实现当前Transform的变换,不会影响到子物体。比如汽车开车时,其他客户端仅能看到车整体的移动,看不到车轮的移动。

*
实例分析

创建角色

1.创建主角对象,这里使用了Capsule、Cube和Cylinder分别代表身体、眼镜和枪搭成一个简单的人物,注意Cube
的Z轴要为正值,这样才能保证游戏对象是面向“前方”的,眼镜是戴在眼镜上而不是后脑勺。



2.创建一个Canvas,并将它的Render Mode修改为World级别,将Scale调至(0.01,0.01,0.01),并通过修改Position
以调整至主角的头上方。



3.为主角添加一个Slider以表示血条,将其中的Fill Area和Handle Slide Area删去,仅保留Background,

       ========》  

4.修改血条的背景色为红色,填充色为绿色,满血时就是绿色,每掉一点血,就会露出一点红色。



血条面向摄像机

新建脚本LookAtCamera,并挂在Player下的Canvas上。

Camera.main 代表带有MainCamera 标签的Camera组件,一般一个场景中只有一个。

transform.LookAt(目标transform),使物体的正方向中心始终面对这个点,也就是“看向”目标点。
public class LookAtCamera : MonoBehaviour { // Update is called once per frame
void Update () { transform.LookAt(Camera.main.transform); } }
角色控制

 

1.新建一个脚本Player Controller挂在Player下,并继承自NetworkBehaviour
。当继承自NetworkBehaviour时,自动添加一个组件Network Identity。



这是角色控制的完整代码。
using UnityEngine; using System.Collections; using UnityEngine.Networking; ///
<summary> /// NetworkBehaviour是Unet联网过程中,所有可操控对象必须继承的类 ///
他提供了额外的生命周期函数及联网相关的方法与属性、特性 /// NetworkBehaviour本身是继承自MonoBehaviour ///
</summary> public class PlayerController : NetworkBehaviour { public GameObject
bulletPrefab; public Transform bulletSpawn; // Update is called once per frame
void Update() { if (isLocalPlayer ==
false)//isLocalPlayer用于判断是否是当前客户端进行操作,以防止误操作别人的角色 { return;//如果不是当前对象,则不执行下述代码
} float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical");
//transform.Rotate用于控制物体的旋转, //transform.Rotate(旋转的轴向*单位时间内旋转的角度*旋转的速度)
//当前为匀速转动 transform.Rotate(Vector3.up * (h * 120) * Time.deltaTime);
//transform.Translate用于控制物体的位移 //transform.Translate(位移的方向*单位时间内位移的距离*位移的速度)
//当前为匀速位移 transform.Translate(Vector3.forward * v * 3 * Time.deltaTime);
//点击空格时,开火 if (Input.GetKeyDown(KeyCode.Space)) { CmdFire(); } } /// <summary>
/// 当客户端第一次进入游戏场景时调用 /// </summary> public override void OnStartLocalPlayer() {
//将自己变蓝以区别敌我 GetComponent<MeshRenderer>().material.color = Color.blue; } //
凡是希望从一个客户端发出,在各个客户端都能看到的效果,例如开火,都需要使用Command特性,同时方法名需要以Cmd开头 [Command] void
CmdFire()//实际上这个方法在server里面调用,我们只是发出请求,需要记住,目前不要求理解 { //生成个子弹 GameObject bullet
= Instantiate(bulletPrefab, bulletSpawn.position, bulletSpawn.rotation) as
GameObject; //子弹向前飞 bullet.GetComponent<Rigidbody>().velocity =
bullet.transform.forward * 10; //两秒后自动消失 Destroy(bullet, 2);
//关键部分:将当前的物体(bullet)分发到各个客户端,这样其他的客户端才能看到子弹 NetworkServer.Spawn(bullet); } }
几个地方需要注意(从上至下)

*
在Update开始时,必须先检查isLocalPlayer是不是为false,也就是保证自己只能控制自己的角色,否则一个人点击后,所有人都在动

  if (isLocalPlayer == false)
        {
            return;
        }

*

OnStartLocalPlayer会在此客户端加入场景时调用,一般我们不在联网的对象上使用Awake(),Start()方法,可以认为OnStartLocalPlayer就是用于替代Start()方法的,同样的,这个方法也是第一步调用,且只能够调用一次。

       此方法需重载(override)

public override void OnStartLocalPlayer()
    {
              GetComponent<MeshRenderer>().material.color = Color.blue;
    }

* [Command]特性--用于从客户端发出指令,在服务器端执行。
 
同时,它修饰的方法,方法名前面要加上Cmd前缀。凡是你希望对其他客户端的操作,无论是攻击其他客户端还是自己生成一个新物体(自己生成新物体,也可以理解为通知其他客户端自己有这个物体),都需要这样编写。

[Command]
    void CmdFire()
{

    //function

}

*
NetworkServer.Spawn() -- 将物体显示在各个客户端上。正常的小兵、子弹等等这种附属关系的物体都需要以下的步骤

在Lobby场景中,先完成物体的注册。只有注册了的物体才能够使用NetworkServer.Spawn()



使用Instantiate方法实例化对象

[Command]
    void CmdFire()

{
        GameObject bullet = Instantiate(bulletPrefab, bulletSpawn.position,
bulletSpawn.rotation) as GameObject;

}

执行对象所需执行的操作

[Command]
    void CmdFire()
    {
        GameObject bullet = Instantiate(bulletPrefab, bulletSpawn.position,
bulletSpawn.rotation) as GameObject;
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward
* 10;
        Destroy(bullet, 2);
    }

使用NetworkServer.Spawn()在各个客户端上显示出来这个物体。他同时会显示出这个物体的状态(位移、旋转、缩放等)

[Command]
    void CmdFire()
    {
        GameObject bullet = Instantiate(bulletPrefab, bulletSpawn.position,
bulletSpawn.rotation) as GameObject;
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward
* 10;
        Destroy(bullet, 2);
        NetworkServer.Spawn(bullet);
    }

在其他客户端上同步位移

在Player身上加上一个组件——NetworkTransform

选择Transform Sync Mode(Transform同步方式)



 

添加血条

新建一个代码Health,并将它挂在Player身上
public const int maxHealth = 100; [SyncVar(hook="OnChangeHealth") ] public int
currentHealth = maxHealth; public Slider healthSlider; public bool
destroyOnDeath = false; private NetworkStartPosition[] spawnPoints; void
Start() { if (isLocalPlayer) { spawnPoints =
FindObjectsOfType<NetworkStartPosition>(); } } public void TakeDamage(int
damage) { if (isServer == false) return;// 血量的处理只在服务器端执行 currentHealth -=
damage; if (currentHealth <= 0) { if (destroyOnDeath) {
Destroy(this.gameObject); return; } currentHealth = maxHealth;
Debug.Log("Dead"); RpcRespawn(); } } void OnChangeHealth(int health) {
healthSlider.value = health / (float)maxHealth; } [ClientRpc] void RpcRespawn()
{ if (isLocalPlayer == false) return; Vector3 spawnPosition = Vector3.zero; if
(spawnPoints != null && spawnPoints.Length > 0) { spawnPosition =
spawnPoints[Random.Range(0, spawnPoints.Length)].transform.position; }
transform.position = spawnPosition; }
几个地方需要注意(从上至下)

* [SyncVar]--同步变量,这是一个特性,代表这个变量可以受到其他客户端的影响。
   [SyncVar(hook="方法名")],是它的一个派生特性,代表当这个变量发生变化
时,将调用一次hook中提到的方法,如果有参数,默认将此变量作为参数代入方法中。

  特性必须紧挨着修饰的变量或方法

[SyncVar(hook="OnChangeHealth") ]
    public int currentHealth = maxHealth;

  当变量发生变化时,调用方法

[SyncVar(hook="OnChangeHealth") ]
    public int currentHealth = maxHealth;

//...

 void OnChangeHealth(int health)
    {
        healthSlider.value = health / (float)maxHealth;
    }

* isServer--服务端判断,与isLocalPlayer相对,可以判断当前是否为服务端。
  我们当前的方式是让当前主机作为服务端的同时操控一个客户端,其他主机仅操控一个客户端

public void TakeDamage(int damage)
    {

        if (isServer == false) return;

        //....

    }

 当血量已经为0的时候,让你复活,如果是系统生成的敌人,为他勾上destroyOnDeath,这样当他血量为0,他就会消失了

public void TakeDamage(int damage)
    {

        if (isServer == false) return

        currentHealth -= damage;
        if (currentHealth <= 0)
        {
            if (destroyOnDeath)//当系统敌人血量为0
            {
                Destroy(this.gameObject); return;
            }

            currentHealth = maxHealth;
            Debug.Log("Dead");
            RpcRespawn();//重生
        }

    }

*  [ClientRpc]--从服务端发起,在客户端调用。
  这个特性与[Command]相对,它适合用于被动的效果。比如重生(被杀死后移动到某一位置),传送(被移动)等等

  同样的,它修饰的方法名前面需要加上Rpc

  一般这个方法配合if (isLocalPlayer == false)使用

[ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer == false) return;

       //...

    }

 

为子弹添加效果

新建代码Bullet,并挂在子弹的身上,将它保存为Prefab
using UnityEngine; using System.Collections; public class Bullet :
MonoBehaviour { void OnCollisionEnter(Collision col) { GameObject hit =
col.gameObject; Health health = hit.GetComponent<Health>(); if (health != null)
{ health.TakeDamage(10); } Destroy(this.gameObject); } }
* 利用碰撞检测
 void OnCollisionEnter(Collision col)
{

     //function

}

* 试图获得物体身上的Health(血条)
void OnCollisionEnter(Collision col)
    {
        GameObject hit = col.gameObject;
        Health health = hit.GetComponent<Health>();
    }

* 当取得到物体身上的Health组件,就说这这个物体代表敌人,对他造成伤害
void OnCollisionEnter(Collision col)
    {
        GameObject hit = col.gameObject;
        Health health = hit.GetComponent<Health>();

        if (health != null)
        {
            health.TakeDamage(10);
        }
 }

* 子弹撞墙或者撞到地面都可以让他消失
void OnCollisionEnter(Collision col)
    {
        GameObject hit = col.gameObject;
        Health health = hit.GetComponent<Health>();

        if (health != null)
        {
            health.TakeDamage(10);
        }
        Destroy(this.gameObject);
    }

最后处理

将此Player制作为Prefab,并在Main场景中删去

Player会在客户端加载的时候自动生成



 

就这样吧,掰掰。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信