Unity3D|unity3d-多人坦克对战

这次的作业是在上一次坦克大战作业的基础上进行改进,实现多人网络对战版。
视频演示
https://www.bilibili.com/video/av25649733/
游戏实现
游戏主要对象还是利用了坦克预设和子弹预设。如下图:
Unity3D|unity3d-多人坦克对战
文章图片

将原本的脚本都去掉,然后为tank预设添加Network Identity和Network Transform组件,并且把Network Identity的local player authority选项勾上,以允许能够在本地进行控制。然后创建一个Empty对象并挂载Network Manager和Network Manager HUD脚本,作为整个游戏的网络管理对象。同样的道理,子弹也应该添加Network Identity和Network Transform组件,注意子弹不需要勾选local player authority,并且Network Send Rate设为0即可。最后还要在Network Manager的Spawn Info里面注册相应的预设。具体设置如下:
Unity3D|unity3d-多人坦克对战
文章图片

Unity3D|unity3d-多人坦克对战
文章图片

Unity3D|unity3d-多人坦克对战
文章图片

主要代码
为坦克添加控制脚本PlayerMove。Update()函数首先判断是不是本地对象执行,只有是服务端对象执行才可以修改坦克的位置,通过监听按键输入来控制刚体组件相应的速度来实现。当按下空格的时候,坦克开火。我使用了AddForce来控制子弹实例的刚体运动。如果直接在本地执行函数CmdShoot中使用,会出现client端子弹产生后不会运动的效果。(原因可以查一下谷歌:unity3d network transform addforce not working)。这里需要将AddForce函数放在一个ClientRpc函数中调用,这样才可以在所有客户端中同步。
其他的代码比较简单,这里就不累述。注意下两个函数:OnStartClient()和OnStartServer()。这两个函数分别在客户端对象和服务端对象产生时调用,这里我使用它们的目的是将对象的transform传递给main camera,使得main camera可以根据坦克对象的位置自动更新视野。

public class PlayerMove : NetworkBehaviour { public GameObject bulletPrefabs; public override void OnStartClient() { Camera.main.GetComponent().setTarget(this.transform); }public override void OnStartServer() { Camera.main.GetComponent().setTarget(this.transform); }private void FixedUpdate() { if (!isLocalPlayer) return; float offsetX = Input.GetAxis("Horizontal"); float y = this.transform.localEulerAngles.y + offsetX * 5; float x = this.transform.localEulerAngles.x; this.transform.localEulerAngles = new Vector3(x, y, 0); }// Update is called once per frame void Update () { if (!isLocalPlayer) return; if (Input.GetKey(KeyCode.W)) { this.GetComponent().velocity = this.transform.forward * 20; } if (Input.GetKey(KeyCode.S)) { this.GetComponent().velocity = this.transform.forward * -20; } if (Input.GetKeyDown(KeyCode.Space)) { CmdShoot(); } }[Command] void CmdShoot() { var bullet = (GameObject)Instantiate( bulletPrefabs, new Vector3(transform.position.x, 1.5f, transform.position.z) + transform.forward * 1.5f, Quaternion.identity); bullet.transform.forward = transform.forward; NetworkServer.Spawn(bullet); RpcAddForceOnAll(bullet); // make bullet disappear after 2 seconds Destroy(bullet, 2.0f); }[ClientRpc] void RpcAddForceOnAll(GameObject bullet) { bullet.GetComponent().AddForce(bullet.transform.forward * 20, ForceMode.Impulse); } }

Combat组件用于控制坦克血量,health字段设置为SyncVar,表示该变量是服务端同步的。扣血函数TakeDamage只有服务端才可以调用,并且当玩家坦克血量为0之后(这里我设定为承受5次伤害即死亡),会进行重生,RpcRespawn函数会在客户端对象中更新玩家坦克的位置。
public class Combat : NetworkBehaviour {public const int maxHealth = 500; [SyncVar] public int health = maxHealth; public void TakeDamage(int amount) { if (!isServer) return; health -= amount; Debug.Log("health value = "https://www.it610.com/article/+ health.ToString()); if (health <= 0) { health = maxHealth; RpcRespawn(); } }[ClientRpc] void RpcRespawn() { if (isLocalPlayer) { transform.position = Vector3.zero; } } }

BulletManager 用于管理子弹的状态,当子弹发生碰撞或者子弹落到地上都会消失。如果击中敌军坦克还会对其进行扣血。
public class BulletManager : MonoBehaviour {//private float explosionRadius = 3f; private void FixedUpdate() { if(this.transform.position.y <= 0) { Destroy(gameObject); } }void OnCollisionEnter(Collision other) { var hit = other.gameObject; var hitPlayer = hit.GetComponent(); if (hitPlayer != gameObject) { // 根据击中坦克与爆炸中心的距离计算伤害值 //float distance = Vector3.Distance(colliders[i].transform.position, transform.position); //被击中坦克与爆炸中心的距离 //float hurt = 100f / distance; //float current = colliders[i].GetComponent().getHp(); //colliders[i].GetComponent().setHp(current - hurt); var combat = hit.GetComponent(); combat.TakeDamage(100); } Destroy(gameObject); } }

总结
完整代码请到我的github查看:https://github.com/CarolSum/Unity3d-Learning/tree/master/hw10
【Unity3D|unity3d-多人坦克对战】老师课上的简单多人游戏教程很详细,参考意义很大~这次作业也比较简单,能做出一个小型多人对战游戏很不错~毕竟小时候也是玩这种游戏大的。但是这远远不够,想要做出更有意义的游戏还是得继续深入学习,当然这得放到期末考之后啦~

    推荐阅读