Unity學(xué)習(xí)筆記09聯(lián)網(wǎng)
網(wǎng)絡(luò)基礎(chǔ)
多為策略游戲:MMORTS ? ? ?角色扮演游戲:MMORPG
聯(lián)網(wǎng)功能
1.現(xiàn)在的游戲引擎都提供了基本的聯(lián)網(wǎng)功能,比如LAN方式的聯(lián)網(wǎng)對(duì)戰(zhàn)。
2.大規(guī)模多人在線游戲的網(wǎng)絡(luò)處理很復(fù)雜。
局域網(wǎng)
一般沒有專用的服務(wù)器,通常是一臺(tái)機(jī)器充當(dāng)兩個(gè)角色,其他機(jī)器連接到該機(jī)器上,構(gòu)成局域網(wǎng),穩(wěn)定性
比較高。
廣域網(wǎng)
一般有一臺(tái)專用的,性能極高的作為服務(wù)器,其他所有的機(jī)器通過互聯(lián)網(wǎng)遠(yuǎn)程連接到該機(jī)器上。
游戲聯(lián)網(wǎng)的網(wǎng)絡(luò)架構(gòu)(廣域網(wǎng)下):一般是這兩種

分組交換技術(shù)
互聯(lián)網(wǎng)是一種分組交換并且容錯(cuò)的網(wǎng)絡(luò)系統(tǒng)
1.分組交換(信息被分解為多個(gè)小的包(幾個(gè)字節(jié)或千字節(jié)的大?。┓謩e發(fā)送出去。
2.容錯(cuò)(數(shù)據(jù)包在網(wǎng)絡(luò)出現(xiàn)錯(cuò)誤或者服務(wù)器發(fā)生故障的情況下仍然可以發(fā)送,如果一個(gè)服務(wù)器出現(xiàn)故障,數(shù)據(jù)包將
使用其他路徑到達(dá)目的地)
互聯(lián)網(wǎng)的協(xié)議
可以分為3層
1,最底層的是IP協(xié)議(用于報(bào)文交換網(wǎng)絡(luò)的一種面向數(shù)據(jù)的協(xié)議,定義了數(shù)據(jù)包在網(wǎng)際傳送的格式)
2,UDP協(xié)議或者TCP協(xié)議。
UDP或者TCP層將大的數(shù)據(jù)包傳給IP,IP將數(shù)據(jù)包分割為小的子數(shù)據(jù)包,為每個(gè)數(shù)據(jù)包加上一個(gè)信封再發(fā)送出去

3,最頂層的是一些應(yīng)用層協(xié)議。
推薦書籍:

聯(lián)網(wǎng)功能實(shí)現(xiàn):使用游戲引擎自帶的聯(lián)網(wǎng)模塊或者使用底層網(wǎng)絡(luò)接口自己實(shí)現(xiàn)功能
玩家連線(聯(lián)網(wǎng))
游戲聯(lián)網(wǎng)需要的幾個(gè)概念
1.網(wǎng)絡(luò)管理器(管理聯(lián)網(wǎng)物體)
2.聯(lián)網(wǎng)用戶界面
3.具備聯(lián)網(wǎng)功能的玩家預(yù)制件
4.具有聯(lián)網(wǎng)能力的腳本和游戲物體
客戶端,服務(wù)器
客戶端:連接到服務(wù)器的游戲?qū)嵗?/p>
服務(wù)器:專用服務(wù)器(廣域網(wǎng)),主機(jī)服務(wù)器(局域網(wǎng))
簡(jiǎn)單實(shí)踐
在unity的Package Manager中下載安裝Multiplayer ? HLAPI。
新建一個(gè)空物體,為其添加NetworkManager和NetworkManagerHUD組件。
制作用來聯(lián)網(wǎng)的預(yù)制件,為其添加NetworkIdentity(需要將Local Player Authority點(diǎn)上,組件是用來識(shí)別聯(lián)網(wǎng)的物
體)組件和NetworkTransfrom(可以自動(dòng)的將物體的變換在網(wǎng)絡(luò)之間進(jìn)行同步傳輸)。還添加Network Animator
組件(可以自動(dòng)的將物體的動(dòng)畫在網(wǎng)絡(luò)之間進(jìn)行同步傳輸)。但需要一個(gè)動(dòng)畫控制器,也可以直接將預(yù)制件拖拽過
去,勾選所需的屬性即可。
修改腳本的聯(lián)網(wǎng)功能,將預(yù)制件拖拽給NetworkManager的Player Prefab。
運(yùn)行游戲后,點(diǎn)擊LAN Host(H)生成一個(gè)本地主機(jī),要聯(lián)網(wǎng)的話至少需要兩個(gè)機(jī)器來聯(lián)網(wǎng),可以將當(dāng)前場(chǎng)景再生成
一個(gè)副本(file--build and run,需要為其選擇文件存儲(chǔ)位置),然后運(yùn)行兩個(gè)游戲,一個(gè)作為服務(wù)器,一個(gè)作為客
戶端,生成角色后可以在對(duì)方的場(chǎng)景里看到。
玩家角色比較特殊, 每個(gè)玩家控制自己本地的實(shí)例,然后由服務(wù)器端來完成同步
游戲物體同步
網(wǎng)絡(luò)權(quán)限Network ?Authority
1.客戶端和服務(wù)器都可以控制游戲物體行為
2.網(wǎng)絡(luò)權(quán)限指的是游戲物體如何以及在哪里被管理
3.默認(rèn)是服務(wù)器端(玩家角色特殊,是在客戶端)
動(dòng)態(tài)生成游戲物體
1.非聯(lián)網(wǎng)物體使用GameObject.Instantiate生成物體(動(dòng)態(tài)生成游戲物體:先制作好預(yù)制件,然后使用這個(gè)函數(shù))
2.而聯(lián)網(wǎng)物體需要由服務(wù)器端來生成(使用spawn方式生成,生成的物體交由Network Manager管理)
聯(lián)網(wǎng)和非聯(lián)網(wǎng)游戲物體
聯(lián)網(wǎng)游戲的場(chǎng)景中包含聯(lián)網(wǎng)和非聯(lián)網(wǎng)物體
1.聯(lián)網(wǎng)的物體需要在全網(wǎng)同步。
2.非聯(lián)網(wǎng)的物體只運(yùn)行于本地(靜態(tài)物體,比如石塊、樓梯。不需要同步的物體,比如隨風(fēng)擺動(dòng)的樹木、草叢或者
云朵)。
聯(lián)網(wǎng)物體
1.需要包含Network ldentity組件。
2.還需要開發(fā)人員指定物體的哪些屬性需要同步。
3.哪些屬性需要同步取決于游戲內(nèi)容(對(duì)于玩家或者非玩家角色的位置、朝向。帶有動(dòng)畫物體的動(dòng)畫狀態(tài)?!?shù)
值,比如游戲剩余時(shí)長(zhǎng)、玩家的能量值)
屬性同步
1.可以自動(dòng)同步的屬性
Network Transform組件來同步物體變換屬性。Network Animator組件來同步動(dòng)畫屬性。
2.無法自動(dòng)同步的屬性(在腳本里面指定)
簡(jiǎn)單實(shí)踐
Network transfrom中的Network send rate 調(diào)整為零,代表剛開始時(shí)自動(dòng)同步,之后則通過腳本來控制同步。如
果完全自動(dòng)同步的話對(duì)網(wǎng)絡(luò)的要求很高。
新建一個(gè)子彈預(yù)制件拖拽給網(wǎng)絡(luò)管理器的Registered ?Spawnable ?Prefabs(代表可網(wǎng)絡(luò)生成的物體)
修改玩家控制腳本,讓角色可以發(fā)射子彈,修改后記得將預(yù)制件賦給該腳本
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.
? ? ? ?//發(fā)射子彈
? ? ? ?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)//如果不是本地要控制的玩家角色的話,就不進(jìn)行后面的處理
? ? ? ? ? ?{
? ? ? ? ? ? ? ?return;
? ? ? ? ? ?}
? ? ? ? ? ?if (!m_Jump)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?// Fixed update is called in sync with physics
? ? ? ?private void FixedUpdate()
? ? ? ?{
? ? ? ? ? ?if (!isLocalPlayer)//如果不是本地要控制的玩家角色的話,就不進(jìn)行后面的處理
? ? ? ? ? ?{
? ? ? ? ? ? ? ?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]//加上這個(gè)前綴代表下面的函數(shù)是由客戶端發(fā)起調(diào)用,但是在服務(wù)器端執(zhí)行
? ? ? ?public void CmdFire()//必須加Cmd前綴,,,子彈聯(lián)網(wǎng)
? ? ? ?{
? ? ? ? ? ?var bullet = Instantiate(bulletPrefab, transform.position + transform.up + transform.forward * 2, transform.rotation) as GameObject;
? ? ? ? ? ?NetworkServer.Spawn(bullet);//生成并同步到各客戶端
? ? ? ?}
? ?}
}
玩家生命值腳本,賦給玩家預(yù)制件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Health : NetworkBehaviour//有關(guān)網(wǎng)絡(luò)的操作需要繼承該類
{
? ?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時(shí)重置玩家
? ? ? ?}
? ?}
? ?[ClientRpc]//表示下面的函數(shù)是由服務(wù)器發(fā)起調(diào)用的,但是在客戶端來執(zhí)行
? ?public void RpcRespawn()//必須加上Rpc前綴
? ?{
? ? ? ?if(isLocalPlayer)
? ? ? ?{
? ? ? ? ? ?transform.position = Vector3.zero;//將位置重置為原點(diǎn)
? ? ? ? ? ?hp = hp_max;
? ? ? ?}
? ?}
? ?// Start is called before the first frame update
? ?void Start()
? ?{
? ? ? ?
? ?}
? ?// Update is called once per frame
? ?void Update()
? ?{
? ? ? ?
? ?}
}
子彈腳本,賦給子彈預(yù)制件
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)//子彈預(yù)制件的Is trigger要點(diǎn)上
? ?{
? ? ? ?other.SendMessage("GetDamage",60,SendMessageOptions.DontRequireReceiver);//如果碰到的物體身上有GetDamage的話,執(zhí)行健康值減少60的函數(shù),因?yàn)橛锌赡艽虻狡渌奈矬w
? ?}
}
角色同步
(非玩家角色,每一個(gè)客戶端并不具備直接控制這些角色的能力,非玩家角色直接運(yùn)行于服務(wù)器端,只有服務(wù)器端
才能對(duì)其進(jìn)行控制以及網(wǎng)絡(luò)同步)
簡(jiǎn)單實(shí)踐
建立敵人預(yù)制件和腳本。
Instantiate
使用預(yù)制件在場(chǎng)景中生成物體,共有三個(gè)參數(shù),一是預(yù)制件,二是生成物體的位置,三是生成物體的朝向。
示例:
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;//敵人預(yù)制件
? ?public int enemyNum = 5;//生成的敵人數(shù)量
? ?public override void OnStartServer()//服務(wù)器端開始運(yùn)行的時(shí)候生成敵人
? ?{
? ? ? ?for(int i=0;i<enemyNum;++i)
? ? ? ?{
? ? ? ? ? ?var unit_pos = Random.insideUnitCircle;//隨機(jī)生成位置,使用這個(gè)函數(shù)是生成在一個(gè)單位圓內(nèi),unit_pos代表一個(gè)二維向量類型
? ? ? ? ? ?unit_pos = unit_pos * 6;//表示單位為6
? ? ? ? ? ?var rnd_rot = Quaternion.Euler(new Vector3(0, Random.Range(-180, 180), 0));//生成一個(gè)隨機(jī)的朝向
? ? ? ? ? ?var enemy = Instantiate(enemyProfab, new Vector3(unit_pos.x, 0, unit_pos.y), rnd_rot);
? ? ? ? ? ?NetworkServer.Spawn(enemy);//由服務(wù)器端生成敵人
? ? ? ?}
? ?}
}
生成后要將敵人預(yù)制件拖拽上去。
NetworkIdentity中的Server ?Only 如果勾選則表示阻止客戶端有權(quán)利生成公用的敵人。
修改Health腳本,讓子彈可以打到敵人,并且需要將敵人預(yù)制件的IsEnemy點(diǎn)上,表示是敵人,不是玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Health : NetworkBehaviour//有關(guān)網(wǎng)絡(luò)的操作需要繼承該類
{
? ?private static int hp_max = 100;//最大生命值
? ?public int hp = hp_max;//初始生命值
? ?public bool isEnemy = false;//標(biāo)識(shí)角色是不是敵人
? ?public void GetDamage(int dmg)
? ?{
? ? ? ?if(!isServer)
? ? ? ?{
? ? ? ? ? ?return;
? ? ? ?}
? ? ? ?hp -= dmg;
? ? ? ?if(hp<0)
? ? ? ?{
? ? ? ? ? ?if (isEnemy) Destroy(gameObject);//如果敵人血量小于0讓其消失
? ? ? ? ? ?RpcRespawn();//生命值小于0時(shí)重置玩家
? ? ? ?}
? ?}
? ?[ClientRpc]//表示下面的函數(shù)是由服務(wù)器發(fā)起調(diào)用的,但是在客戶端來執(zhí)行
? ?public void RpcRespawn()//必須加上Rpc前綴
? ?{
? ? ? ?if(isLocalPlayer)
? ? ? ?{
? ? ? ? ? ?transform.position = Vector3.zero;//將位置重置為原點(diǎn)
? ? ? ? ? ?hp = hp_max;
? ? ? ?}
? ?}
? ?// Start is called before the first frame update
? ?void Start()
? ?{
? ? ? ?
? ?}
? ?// Update is called once per frame
? ?void Update()
? ?{
? ? ? ?
? ?}
}