mirror文档:
Mirror Networking - Mirror
1.导入mirror资源
2.导入网络组件
只有一个场景,在线和离线加载同一个
3.配置场景
新建一个平面,设置成:
添加任意个初始点,并加入NetworkStartPosition
加入一个游戏对象,比如胶囊,然后加入网络id(networkidentity)和networkTransform
重命名并且加入脚本
将一个游戏物体初始化后,就加入工程中的预制体文件中,成为一个初始完随时可以使用的游戏物体资源
然后将游戏物体拖拽到networkmanager的游戏预制体处
因为设置了游戏物体生成的初始位点,所以将玩家生成方法设置成Round Robin(轮循机制),然后将场景中的游戏对象删除,在多人游戏中,自动根据预先设置的点位自动生成
在游戏物体的脚本中添加以下代码:
using Mirror;
using UnityEngine;
namespace QuickStart
{
public class PlayerScript : NetworkBehaviour
{
public override void OnStartLocalPlayer()
{
Camera.main.transform.SetParent(transform);
Camera.main.transform.localPosition = new Vector3(0, 0, 0);
}
void Update()
{
if (!isLocalPlayer)
{
return;
}
float moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;
float moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4f;
transform.Rotate(0, moveX, 0);
transform.Translate(0, 0, moveZ);
}
}
}
4.简易多人测试
都进入localhost,多人测试成功
5.名字置于游戏物体上方
先新建一个空对象FloatingInfo,表示悬浮数据的载体(名字、血条等),该载体主要负责数据整体的Transform
6.更新PlayerScript脚本
using Mirror;
using UnityEngine;
namespace QuickStart
{
public class PlayerScript : NetworkBehaviour
{
public TextMesh playerNameText;
public GameObject floatingInfo;
private Material playerMaterialClone;
[SyncVar(hook = nameof(OnNameChanged))]
public string playerName;
[SyncVar(hook = nameof(OnColorChanged))]
public Color playerColor = Color.white;
void OnNameChanged(string _Old, string _New)
{
playerNameText.text = playerName;
}
void OnColorChanged(Color _Old, Color _New)
{
playerNameText.color = _New;
playerMaterialClone = new Material(GetComponent<Renderer>().material);
playerMaterialClone.color = _New;
GetComponent<Renderer>().material = playerMaterialClone;
}
public override void OnStartLocalPlayer()
{
Camera.main.transform.SetParent(transform);
Camera.main.transform.localPosition = new Vector3(0, 0, 0);
floatingInfo.transform.localPosition = new Vector3(0, -0.3f, 0.6f);
floatingInfo.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
string name = "Player" + Random.Range(100, 999);
Color color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));
CmdSetupPlayer(name, color);
}
[Command]
public void CmdSetupPlayer(string _name, Color _col)
{
// player info sent to server, then server updates sync vars which handles it on all clients
playerName = _name;
playerColor = _col;
}
void Update()
{
if (!isLocalPlayer)
{
// make non-local players run this
floatingInfo.transform.LookAt(Camera.main.transform);
return;
}
float moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;
float moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4f;
transform.Rotate(0, moveX, 0);
transform.Translate(0, 0, moveZ);
}
}
}
7.脚本更新后,将游戏对象中新建的子对象映射到脚本中去
测试
8.添加交互部分
加入场景脚本
新建SceneScript文件
using Mirror;
using UnityEngine;
using UnityEngine.UI;
namespace QuickStart
{
public class SceneScript : NetworkBehaviour
{
public Text canvasStatusText;
public PlayerScript playerScript;
[SyncVar(hook = nameof(OnStatusTextChanged))]
public string statusText;
void OnStatusTextChanged(string _Old, string _New)
{
//called from sync var hook, to update info on screen for all players
canvasStatusText.text = statusText;
}
public void ButtonSendMessage()
{
if (playerScript != null)
{
playerScript.CmdSendPlayerMessage();
}
}
}
}
PlayerScript文件中添加或更新函数
private SceneScript sceneScript;
void Awake()
{
//allow all players to run this
sceneScript = GameObject.FindObjectOfType<SceneScript>();
}
[Command]
public void CmdSendPlayerMessage()
{
if (sceneScript)
{
sceneScript.statusText = $"{playerName} says hello {Random.Range(10, 99)}";
}
}
[Command]
public void CmdSetupPlayer(string _name, Color _col)
{
//player info sent to server, then server updates sync vars which handles it on all clients
playerName = _name;
playerColor = _col;
sceneScript.statusText = $"{playerName} joined.";
}
public override void OnStartLocalPlayer()
{
sceneScript.playerScript = this;
//. . . . ^ new line to add here
}
将按钮单击的函数映射给SceneScript的ButtonSendMessage函数
将canvas上的文本映射给SceneScript脚本中做处理
测试
9.武器模块
在PlayerScript中加入如下代码:
private int selectedWeaponLocal = 1;
public GameObject[] weaponArray;
[SyncVar(hook = nameof(OnWeaponChanged))]
public int activeWeaponSynced = 1;
void OnWeaponChanged(int _Old, int _New)
{
// disable old weapon
// in range and not null
if (0 < _Old && _Old < weaponArray.Length && weaponArray[_Old] != null)
{
weaponArray[_Old].SetActive(false);
}
// enable new weapon
// in range and not null
if (0 < _New && _New < weaponArray.Length && weaponArray[_New] != null)
{
weaponArray[_New].SetActive(true);
}
}
[Command]
public void CmdChangeActiveWeapon(int newIndex)
{
activeWeaponSynced = newIndex;
}
void Awake()
{
// disable all weapons
foreach (var item in weaponArray)
{
if (item != null)
{
item.SetActive(false);
}
}
}
在update函数中更新如下代码:
void Update()
{
if (!isLocalPlayer)
{
// make non-local players run this
floatingInfo.transform.LookAt(Camera.main.transform);
return;
}
float moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;
float moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4f;
transform.Rotate(0, moveX, 0);
transform.Translate(0, 0, moveZ);
if (Input.GetButtonDown("Fire2")) //Fire2 is mouse 2nd click and left alt
{
selectedWeaponLocal += 1;
if (selectedWeaponLocal > weaponArray.Length)
{
selectedWeaponLocal = 1;
}
CmdChangeActiveWeapon(selectedWeaponLocal);
}
}
在场景中编辑武器对象物体
新建一个空物体WeaponsHolder,用于存放武器的容器,然后在该容器中新建两个立方体weapon1和weapon2。
将武器添加到数武器数组中
将weapon2 disable了
测试,左alt切换武器