携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
玩家游戏对象
Mirror对于客户端的连接抽象成了一个Player Game Object,当客户端连上服务器并开始游戏后,就会生成一个玩家游戏对象。这个玩家游戏对象同样是一个网络游戏对象,是存在于服务器上的,但是不同于普通的网络游戏对象,该对象能从客户端接受到玩家的控制信息,并且通过command发送到服务器,然后服务器再将该对象同步到其他客户端。
客户端脚本操作
在客户端的Network Behaviour脚本中,我们通过 isLocalPlayer 来判断当前操作的player game object是否是自己的玩家游戏对象,即Local player对象。如果是local player,则我们可以对player进行操作,比如处理输入,根据Player的位置更新camera,或者任何针对本地玩家的操作。
客户端主动操作的转发
对于Local player的操作,客户端通过command发送到服务器,然后服务器再进行转发,将local player在服务器上对应的player game object的状态同步到其他客户端上对应的game object中。对于位置和动画,可直接使用NetworkTransform和NetworkAniamtor进行同步,而其他属性可通过SyncVars进行同步,并且当某客户端发送Command之后,服务器上的方法被调用,此方法不一定是改变该Player Object的状态,有可能是触发某事件,则可通过在服务器上进行ClinetRPC调用,来让特定的或所有的客户端执行某个方法。通过这一机制,我们写网络游戏程序时,只要关注于当前本地player需要进行的操作,然后去响应服务器给客户端的远程RPC调用,再加上属性的同步,基本就可以完成游戏的主要网络交互了。
一个例子
我们以Mirror Sample中的Tanks项目为例。玩家连上服务后,会生成一个Tank对象,该对象使用Tank预制体,该预制体上挂载了Tank脚本:
public class Tank : NetworkBehaviour
{
[Header("Components")]
public NavMeshAgent agent;
public Animator animator;
public TextMesh healthBar;
public Transform turret;
[Header("Movement")]
public float rotationSpeed = 100;
[Header("Firing")]
public KeyCode shootKey = KeyCode.Space;
public GameObject projectilePrefab;
public Transform projectileMount;
[Header("Stats")]
[SyncVar] public int health = 4;
void Update()
{
// always update health bar.
// (SyncVar hook would only update on clients, not on server)
healthBar.text = new string('-', health);
// movement for local player
if (isLocalPlayer)
{
// rotate
float horizontal = Input.GetAxis("Horizontal");
transform.Rotate(0, horizontal * rotationSpeed * Time.deltaTime, 0);
// move
float vertical = Input.GetAxis("Vertical");
Vector3 forward = transform.TransformDirection(Vector3.forward);
agent.velocity = forward * Mathf.Max(vertical, 0) * agent.speed;
animator.SetBool("Moving", agent.velocity != Vector3.zero);
// shoot
if (Input.GetKeyDown(shootKey))
{
CmdFire();
}
RotateTurret();
}
}
// this is called on the server
[Command]
void CmdFire()
{
GameObject projectile = Instantiate(projectilePrefab, projectileMount.position, projectileMount.rotation);
NetworkServer.Spawn(projectile);
RpcOnFire();
}
// this is called on the tank that fired for all observers
[ClientRpc]
void RpcOnFire()
{
animator.SetTrigger("Shoot");
}
在Update方法中,判断 isLocalPlayer 来对tank进行旋转和移动操作,并且当按下开火键时,执行CmdFire命令,该命令是在server上执行的,该命令中会调用 RpcOnFire方法,该方法是在所有客户端上执行的。