Networking_FpsDemo

一、创建场景。
命名场景为Main
二、创建NetWorkManager空对象。
并添加脚本NetWorkManagerHUD
NetWorkManager
The High Level API
三、创建游戏同步Player三维对象。
并添脚本 NetworkIdentity,并将其 Local Player Authority参数设置为True。将Player制作成预制体,然后将其从场景中删除(Player Objectshttps://docs.unity3d.com/Manual/UNetPlayers.html?_ga=1.58169839.433409425.1476685957
四、将Player预制体注册到NetWorkManager上
拖动Player到NetWorkManager的SpawnInfo的PlayerPrefab属性上
五、为Player添加运动操作脚本
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void Update()
    {
        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);
    }
}
六、测试本地以 LAN Host启动游戏。
Network System Conceptshttps://docs.unity3d.com/Manual/UNetConcepts.html?_ga=1.124244047.433409425.1476685957),然后打包客户端以client模式加入运行。
七、实现只控制本地对象。
将MonoBehaiver替换为NetWorkBehaiver,并判断是否是本地对象,否则返回。(NetWorkBehaiver:https://docs.unity3d.com/ScriptReference/Networking.NetworkBehaviour.html?_ga=1.56679535.433409425.1476685957)
然后在Player预制体上添加NetWorkTranform(NetWorkTransform:https://docs.unity3d.com/Manual/class-NetworkTransform.html?_ga=1.157733375.433409425.1476685957
八、多人在线测试。
修改 Network Send Rate并再次测试,观察流畅度
九、识别本地玩家。
在脚本中添加:
public override void OnStartLocalPlayer()
{
    GetComponent().material.color = Color.blue;
}
可以在本地本地玩家创建后将其颜色转换为蓝色(Object Spawninghttps://docs.unity3d.com/Manual/UNetSpawning.html?_ga=1.94715225.433409425.1476685957
十、创建本地射击功能。制作子弹预制体,并添加刚体组件,修改PlayerController脚本,添加生成子弹的功能:using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }
    }


    void Fire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent<RigidBody>().velocity = bullet.transform.forward * 6;

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);        
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
},将子弹预制体和子弹生成点拖入到脚本上
十一、多人在线射击。
在子弹上添加NetwordIdentity,NetworkTransform.并将NetWorkTranform中的NetworkSendRate
设置为一,NetWorkIdentity上LocalPlayerAuthorit设置为True.
此时进一步修改PlayerController脚本,实现只创建本地。
using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            CmdFire();
        }
    }

    // This [Command] code is called on the Client …
    // … but it is run on the Server!
    [Command]
    void CmdFire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

        // Spawn the bullet on the Clients
        NetworkServer.Spawn(bullet);

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
十二,本地人物血量。
添加子弹逻辑代码,实现碰撞后销毁子弹
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {
   
    void OnCollisionEnter()
    {
        Destroy(gameObject);
    }
}

添加人物血量脚本,并有受到攻击掉血的方法。
using UnityEngine;
public class Health : MonoBehaviour
{
    public const int maxHealth = 100;
    public int currentHealth = maxHealth;
    public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }
    }
}
当子弹和人物发生碰撞时,需要调用人物Heath脚本的TackDamage方法,修改子弹脚本为:
using UnityEngine;
using System.Collections;
public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var health = hit.GetComponent<Health>();
        if (health != null)
        {
            health.TakeDamage(10);
        }
        Destroy(gameObject);
    }
}
添加世界座标中血量条,增加一个3维canvas,作为Player的子物体,命名为HealthBarCanvas.将大小设置为(0.01,0.01,0.01)座标设置为(0,1.5,0).添加两个Image分别命名为Background和Foreground,颜色设置为红色和蓝色,设置父子关系,将Forground的中心点设置为(0,0.5)锚点设置为(0,0.5)(0,0.5)。
修改Health脚本更新血量显示
using UnityEngine;
public class Health : MonoBehaviour
{
    public const int maxHealth = 100;
    public int currentHealth = maxHealth;
    public RectTransform healthBar;
    public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }
        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
}
记得将血条foreground赋予脚本。
血条需要看到摄像机才行,在血条上添加脚本Billboard
using UnityEngine;
using System.Collections;

public class Billboard : MonoBehaviour {

    void Update () {
        transform.LookAt(Camera.main.transform);
    }
}
此时由于血量没有进行同步,子弹虽然在每个客户端里者存在,但由于碰撞的不同血量可能会完全不同
十三、血量同步
修改Health脚本修改为掉血只在服务端运行,然后通过    [SyncVar]  来进行变量同步到客户端。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
public class Health : NetworkBehaviour
{
    public const int maxHealth = 100;
    [SyncVar]
    public int currentHealth = maxHealth;
    public RectTransform healthBar;
    public void TakeDamage(int amount)
    {
        if (!isServer)
        {
            return;
        }
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }
        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
}
当作服务端的客户端运行时可以看到血量的变化,因为currentHealth虽然同步了,但并没有触发血量变化的事件。利用 SyncVar hook 可以在变量发生变化后,同步到UI.
Health脚本修改为如下 :
十四,重生
在服务端调用 ClientRpc 同步到客户端(和Command相反)
实现当血量小于0时,在原点满血复活效果(如果服务端仅仅将某个Player重置到原点,客户端会因为NetWorkTransform重写这个Player的座标)

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
public class Health : NetworkBehaviour
{
    public const int maxHealth = 100;
    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;
    public RectTransform healthBar;
    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = maxHealth;
            // called on the Server, but invoked on the Clients
            RpcRespawn();
        }
    }
    void OnChangeHealth(int currentHealth)
    {
        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // move back to zero location
            transform.position = Vector3.zero;
        }
    }
}

十四,非Player对象的同步
非Player的游戏对象由Server来控制,下面以ai敌人为例。
创建一个EnemySpawner空物体,NetworkIdentity 设置 Server Only为True,添加EnemySpawner 脚本来随机生成敌人 重写 OnStartServer方法
using UnityEngine;
using UnityEngine.Networking;
public class EnemySpawner : NetworkBehaviour
{
    public GameObject enemyPrefab;
    public int numberOfEnemies;
    public override void OnStartServer()
    {
        for (int i = 0; i < numberOfEnemies; i++)
        {
            var spawnPosition = new Vector3(
                Random.Range(-8.0f, 8.0f),
                0.0f,
                Random.Range(-8.0f, 8.0f));
            var spawnRotation = Quaternion.Euler(
                0.0f,
                Random.Range(0, 180),
                0.0f);
            var enemy = (GameObject)Instantiate(enemyPrefab, spawnPosition, spawnRotation);
            NetworkServer.Spawn(enemy);
        }
    }
}
制作敌人,将场景中的Player去掉PlayerController并适当修改造型后,拖回到Project面版中并命名为Enemy。
然后将Enemy赋予EnemySpawner并注册到NetWorkManager中的SpawnInfo的Spanwnableprefab中(注意Enemy的NetWorkIdentity还是需要选择LocalPlayerAuthority,才能同步到每个场景中)
十五、销毁敌人
攻击敌人到血量为0是,要上敌人销毁,只需要将Health做适当修改:(提供destroyOnDeath可选择)
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
public class Health : NetworkBehaviour
{
    public const int maxHealth = 100;
    public bool destroyOnDeath;
    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;
    public RectTransform healthBar;
    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            if (destroyOnDeath)
            {
                Destroy(gameObject);
            }
            else
            {
                currentHealth = maxHealth;
                // called on the Server, will be invoked on the Clients
                RpcRespawn();
            }
        }
    }
    void OnChangeHealth(int currentHealth)
    {
        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // Set the player’s position to origin
            transform.position = Vector3.zero;
        }
    }
}
十六、生成与再生成
游戏对象生成点的控制可以利用 NetworkStartPosition 来实现,只需要在从预先设置好座标点,当Player生成时,就可以选择性的在这些点之间进行生成。
同时,NetworkManager身上有 Random and Round Robin两和生成方式,分别是随机在生成点之间进行生成,和顺序在这些点之间生成。
当需要重新设置生成点,则需要用用户自行找到生成点。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
public class Health : NetworkBehaviour
{
    public const int maxHealth = 100;
    public bool destroyOnDeath;
    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;
    public RectTransform healthBar;
    private NetworkStartPosition[] spawnPoints;
    void Start()
    {
        if (isLocalPlayer)
        {
            spawnPoints = FindObjectsOfType<NetworkStartPosition>();
        }
    }
    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            if (destroyOnDeath)
            {
                Destroy(gameObject);
            }
            else
            {
                currentHealth = maxHealth;
                // called on the Server, invoked on the Clients
                RpcRespawn();
            }
        }
    }
    void OnChangeHealth(int currentHealth)
    {
        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // Set the spawn point to origin as a default value
            Vector3 spawnPoint = Vector3.zero;
            // If there is a spawn point array and the array is not empty, pick one at random
            if (spawnPoints != null && spawnPoints.Length > 0)
            {
                spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)].transform.position;
            }
            // Set the player’s position to the chosen spawn point
            transform.position = spawnPoint;
        }
    }
}
发布了32 篇原创文章 · 获赞 12 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章