mirror--tankWar

一、创建离线场景

  • 1、创建新项目,导入mirror,创建场景重命名为OfflineScenes
  • 2、从Prefabs文件夹中,将预制体LevelArt拖拽到场景中,LevelArt有光源,删除场景中自带的光源
  • 4、从models文件夹中,将Tank拖拽到场景中,调试好合适的位置,也可以拖拽其他的模型布置场景
  • 5、创建canvas,修改UI Scale Mode选项为:scale with screen size,下面的尺寸根据自己的需求更改,我打包出来的是4:3的界面,创建输入框--输入姓名,3个滑杆--调整颜色,一个按钮,其余的自做调整,
  • 6、创建一个空对象,重命名为OfflineManager,创建的脚本OffLineConfig.cs,编写代码,实现功能:tank旋转、拖动滑杆更新坦克颜色、保存输入框输入的姓名、坦克颜色,切换场景
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class OffLineConfig : MonoBehaviour
{
    // 坦克的Transform组件
    public Transform tank;
    // 坦克旋转速度
    public float speedR;
    // 坦克材质
    public Material materialTank;
    // 滑杆数组
    public Slider[] color;
    // 名字
    public Text playerNameText;
    // 提示信息
    public Text markedText;
    // 坦克颜色
    private Color tankColor;
  

    // Update is called once per frame
    void Update()
    {
        // 加负号是为了绕y轴逆时针旋转
        tank.Rotate(-Vector3.up * Time.deltaTime * speedR);
        // 修改颜色
        tankColor.r = color[0].value /255f;
        tankColor.g = color[1].value /255f;
        tankColor.b = color[2].value /255f;
        materialTank.color = tankColor;
    }

    public void clickConfigBtn()
    {
        // 如果姓名为空
        if(playerNameText.text == "")
        {
            // 提示
            markedText.color = Color.red;
            markedText.text = "名字不能为空!!!";
        }
        else
        {
            // 记录姓名、颜色
            PlayerPrefs.SetString("playerName", playerNameText.text);
            PlayerPrefs.SetFloat("tankR", tankColor.r);
            PlayerPrefs.SetFloat("tankG", tankColor.g);
            PlayerPrefs.SetFloat("tankB", tankColor.b);
            // 切换场景
            SceneManager.LoadScene(1);
        }
    }
}
  •  7、一个离线场景,一个主场景,都放到build中,这样才能切换场景,将脚本OffLineConfig.cs绑定到OfflineManager物体上,将需要拖拽的物体拖拽到

 二、创建主场景

  • 1、创建新场景,重命名为main,创建一个空对象,重命名为NetworkManager,添加脚本NetworkManagerHUD,会自动再添加两个组件,将可以修改Server Tick Rate,OfflineScenes场景拖拽到Offline Scenes中
  •  2、tank初始化

    • 将Tank拖拽到场景中,调整与摄像机的位置

    • 坦克添加刚体组件,坦克添加碰撞盒子,设置位置为(0,0.95,0),大小为(1.51,1.71,1.62),注意:碰撞盒子不能紧挨地面,容易检测坦克与地面发生碰撞导致坦克无法移动。
    • 在坦克里创建一个3D Text,重命名为playerName,调整位置,添加NetworkIdentity、NetworkTransform组件,将坦克做成预制体,将其拖拽到NetworkManager中的Player Prefab

  • 3、 创建脚本TankControl.cs,拖拽到Tank上,编写脚本,先完成的功能是能够同步名字、材质

    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      // 引用mirror
      using Mirror;
      // 继承NetworkBehaviour
      public class TankControl : NetworkBehaviour
      {
          // 3DText,用于显示玩家姓名
          public TextMesh player3DText;
          
          // 坦克材质
          public Material materialTank;
          // 坦克颜色
          private Color tankColor;
      
          // OnStartLocalPlayer 类似star(),只在本地玩家上被调用
          public override void OnStartLocalPlayer()
          {
              player3DText.text = PlayerPrefs.GetString("playerName");
              tankColor.r = PlayerPrefs.GetFloat("tankR");
              tankColor.g = PlayerPrefs.GetFloat("tankG");
              tankColor.b = PlayerPrefs.GetFloat("tankB");
          }
      
      }
    • 将该拖拽的拖拽上去,保存场景,打包,发现名字和材质并没有同步,只是在自己的客户端上更改了
    • 为了同步,我们使用SynVar:用于同步服务器和所有客户端的变量,变量只能在服务器上更改
    • 变量只能在服务其被修改,所以在客户端调用的方法,上面要加上【command],修改代码
    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      // 引用mirror
      using Mirror;
      // 继承NetworkBehaviour
      public class TankControl : NetworkBehaviour
      {
          // 3DText,用于显示玩家姓名
          public TextMesh player3DText;
          
          // 坦克盖材质
          public MeshRenderer RendererTank;
          // 坦克头材质
          public MeshRenderer RendererTankHead;
          // 坦克左轮
          public MeshRenderer RendererTankLeft;
          // 坦克右轮
          public MeshRenderer RendererTankRight;
      
          // 存放
          private Material playerMaterialClone;
      
          // SynVar用于同步服务器和所有客户端的变量,变量只能在服务器上更改
          // hook允许你创建一个在客户端的方法,当客户端上接受到更新的信息后,执行这个方法
          [SyncVar(hook = nameof(OnNameChanged))]
          public string playerName;
          // 名字发生改变
          void OnNameChanged(string _old, string _new)
          {
              player3DText.text = playerName;
          }
      
      
          [SyncVar(hook = nameof(OnColorChanged))]
          public Color playerColor;
      
          // 颜色发生变化TODO
          void OnColorChanged(Color _old, Color _new)
          {
              // 为playerMaterialClone赋值,任何都可以,只要是material类型
              playerMaterialClone = new Material(RendererTank.material);
              playerMaterialClone.color = playerColor;
              RendererTank.material = playerMaterialClone;
              RendererTankHead.material = playerMaterialClone;
              RendererTankLeft.material = playerMaterialClone;
              RendererTankRight.material = playerMaterialClone;
          }
      
          // OnStartLocalPlayer 类似star(),只在本地玩家上被调用
          public override void OnStartLocalPlayer()
          {
              // 获取玩家名字
              string name = PlayerPrefs.GetString("playerName");
              // 获取颜色一个颜色
              Color tankColor = new Color(PlayerPrefs.GetFloat("tankR"), PlayerPrefs.GetFloat("tankG"), PlayerPrefs.GetFloat("tankB"));
              // 调用方法,修改变量要在服务器端
              cmdSetupPlayer(name, tankColor);
          }
      
          //Command,从客户端调用,但是在服务器上运行
          [Command]
          void cmdSetupPlayer(string _name, Color _color)
          {
              //玩家信息发送到服务器,然后服务器更新所有客户端上SyncVar变量
              playerName = _name;
              playerColor = _color;
          }
      }
    • 保存场景,打包,发现名字同步了,但是所有客户端的材质颜色会改成最后一个客户端的颜色
    • 这是因为我一直改的都是TankColour材质的颜色,而所有的客户端坦克上都用的这个材质,只要一个发生变化,都发生变化,所以应该是每一个客户端创建新材质替换TankColour材质,而不是修改TankColour材质
    •  坦克的部分是分开的,所以需要用创建的材质,替换所有部分的材质,修改代码-------成功

    •  
    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      // 引用mirror
      using Mirror;
      // 继承NetworkBehaviour
      public class TankControl : NetworkBehaviour
      {
          // 3DText,用于显示玩家姓名
          public TextMesh player3DText;
          
          // 坦克盖材质
          public MeshRenderer RendererTank;
          // 坦克头材质
          public MeshRenderer RendererTankHead;
          // 坦克左轮
          public MeshRenderer RendererTankLeft;
          // 坦克右轮
          public MeshRenderer RendererTankRight;
      
          // 存放
          private Material playerMaterialClone;
      
          // SynVar用于同步服务器和所有客户端的变量,变量只能在服务器上更改
          // hook允许你创建一个在客户端的方法,当客户端上接受到更新的信息后,执行这个方法
          [SyncVar(hook = nameof(OnNameChanged))]
          public string playerName;
          // 名字发生改变
          void OnNameChanged(string _old, string _new)
          {
              player3DText.text = playerName;
          }
      
      
          [SyncVar(hook = nameof(OnColorChanged))]
          public Color playerColor;
      
          // 颜色发生变化TODO
          void OnColorChanged(Color _old, Color _new)
          {
              // 为playerMaterialClone赋值,任何都可以,只要是material类型
              playerMaterialClone = new Material(RendererTank.material);
              playerMaterialClone.color = playerColor;
              RendererTank.material = playerMaterialClone;
              RendererTankHead.material = playerMaterialClone;
              RendererTankLeft.material = playerMaterialClone;
              RendererTankRight.material = playerMaterialClone;
          }
      
          // OnStartLocalPlayer 类似star(),只在本地玩家上被调用
          public override void OnStartLocalPlayer()
          {
              // 获取玩家名字
              string name = PlayerPrefs.GetString("playerName");
              // 获取颜色一个颜色
              Color tankColor = new Color(PlayerPrefs.GetFloat("tankR"), PlayerPrefs.GetFloat("tankG"), PlayerPrefs.GetFloat("tankB"));
              // 调用方法,修改变量要在服务器端
              cmdSetupPlayer(name, tankColor);
          }
      
          //Command,从客户端调用,但是在服务器上运行
          [Command]
          void cmdSetupPlayer(string _name, Color _color)
          {
              //玩家信息发送到服务器,然后服务器更新所有客户端上SyncVar变量
              playerName = _name;
              playerColor = _color;
          }
      }
  • 4、移动+相机跟随 
    • 看一看下面有没有勾选上
    • 时间有限不详细叙述代码,创建相关变量,

    • 此时代码TankControl.cs的代码
    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      // 引用mirror
      using Mirror;
      // 继承NetworkBehaviour
      public class TankControl : NetworkBehaviour
      {
          // 同步姓名、材质
          // 3DText,用于显示玩家姓名
          public TextMesh player3DText;
          
          // 坦克盖材质
          public MeshRenderer RendererTank;
          // 坦克头材质
          public MeshRenderer RendererTankHead;
          // 坦克左轮
          public MeshRenderer RendererTankLeft;
          // 坦克右轮
          public MeshRenderer RendererTankRight;
          // 存放
          private Material playerMaterialClone;
      
      
      
          // 相机跟随+移动
          private float moveSpeed = 5;  // 坦克移动的速度
          private float turnSpped = 5; // 坦克转向的速度
          private Rigidbody rb;    // 刚体组件
      
          private Vector3 offset;//和相机的相对位置
          Quaternion camRotation;   //记录相机初始角度
       
      
      
          // SynVar用于同步服务器和所有客户端的变量,变量只能在服务器上更改
          // hook允许你创建一个在客户端的方法,当客户端上接受到更新的信息后,执行这个方法
          [SyncVar(hook = nameof(OnNameChanged))]
          public string playerName;
          // 名字发生改变
          void OnNameChanged(string _old, string _new)
          {
              player3DText.text = playerName;
          }
      
      
          [SyncVar(hook = nameof(OnColorChanged))]
          public Color playerColor;
      
          // 颜色发生变化TODO
          void OnColorChanged(Color _old, Color _new)
          {
              // 为playerMaterialClone赋值,任何都可以,只要是material类型
              playerMaterialClone = new Material(RendererTank.material);
              playerMaterialClone.color = playerColor;
              RendererTank.material = playerMaterialClone;
              RendererTankHead.material = playerMaterialClone;
              RendererTankLeft.material = playerMaterialClone;
              RendererTankRight.material = playerMaterialClone;
          }
      
          // OnStartLocalPlayer 类似star(),只在本地玩家上被调用
          public override void OnStartLocalPlayer()
          {
              // 获取玩家名字
              string name = PlayerPrefs.GetString("playerName");
              // 获取颜色一个颜色
              Color tankColor = new Color(PlayerPrefs.GetFloat("tankR"), PlayerPrefs.GetFloat("tankG"), PlayerPrefs.GetFloat("tankB"));
              // 调用方法,修改变量要在服务器端
              cmdSetupPlayer(name, tankColor);
      
      
              rb = GetComponent<Rigidbody>();
              offset = Camera.main.transform.position - transform.position;
              camRotation = Camera.main.transform.rotation;
          }
      
      
          // 物理动作尽量放到FixedUpdate中
          void FixedUpdate()
          {
              // 如果不是本地玩家,不执行下面的代码
              if (!isLocalPlayer) return;
              // 相机跟随
              Camera.main.transform.position = transform.position + offset;
              // 控制转向--转向是y轴方向转向,水平变量控制
              float h = Input.GetAxis("Horizontal");
              // 控制前进
              float v = Input.GetAxis("Vertical");
              // 前进
              rb.velocity = transform.forward * v * moveSpeed;
              // 转向
              rb.angularVelocity = transform.up * h * turnSpped;
      
          }
      
      
          //Command,从客户端调用,但是在服务器上运行
          [Command]
          void cmdSetupPlayer(string _name, Color _color)
          {
              //玩家信息发送到服务器,然后服务器更新所有客户端上SyncVar变量
              playerName = _name;
              playerColor = _color;
          }
      }
  • 5、发射子弹
    • 在tank预制体中创建一个空对象,重命名FirePos,用来做发射子弹的地点,设置位置和角度
    • 从models中添加子弹,为子弹添加碰撞器、刚体组件、将子弹设置成预制体
    • 添加变量,编写代码,拖拽该有的变量
    • 打包、运行,只能在自身的客户生成子弹,无法在所有客户端同步,应该在所有的客户端生成子弹,这里要用到Spawn
    • 修改代码
    •  用Spawn()生成的物体要加上Networkidentity组件,所以给子弹加上Networkidentity组件,并且添加到NetworkManage组件中

    •  打包、运行,只能在自身的客户生成子弹,无法在所有客户端同步,是因为Spawn()需要服务器调用,所以修改代码

    • 此时代码TankControl.cs的代码
    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      // 引用mirror
      using Mirror;
      // 继承NetworkBehaviour
      public class TankControl : NetworkBehaviour
      {
          // 同步姓名、材质
          // 3DText,用于显示玩家姓名
          public TextMesh player3DText;
          
          // 坦克盖材质
          public MeshRenderer RendererTank;
          // 坦克头材质
          public MeshRenderer RendererTankHead;
          // 坦克左轮
          public MeshRenderer RendererTankLeft;
          // 坦克右轮
          public MeshRenderer RendererTankRight;
          // 存放
          private Material playerMaterialClone;
      
      
      
          // 相机跟随+移动
          private float moveSpeed = 5;  // 坦克移动的速度
          private float turnSpped = 5; // 坦克转向的速度
          private Rigidbody rb;    // 刚体组件
      
          private Vector3 offset;//和相机的相对位置
          Quaternion camRotation;   //记录相机初始角度
      
      
          // 发射子弹
          public GameObject shellPrefab;   // 子弹预制体
          public Transform firePoint;     // 发射点的Transform组件
      
      
      
          // SynVar用于同步服务器和所有客户端的变量,变量只能在服务器上更改
          // hook允许你创建一个在客户端的方法,当客户端上接受到更新的信息后,执行这个方法
          [SyncVar(hook = nameof(OnNameChanged))]
          public string playerName;
          // 名字发生改变
          void OnNameChanged(string _old, string _new)
          {
              player3DText.text = playerName;
          }
      
      
          [SyncVar(hook = nameof(OnColorChanged))]
          public Color playerColor;
      
          // 颜色发生变化TODO
          void OnColorChanged(Color _old, Color _new)
          {
              // 为playerMaterialClone赋值,任何都可以,只要是material类型
              playerMaterialClone = new Material(RendererTank.material);
              playerMaterialClone.color = playerColor;
              RendererTank.material = playerMaterialClone;
              RendererTankHead.material = playerMaterialClone;
              RendererTankLeft.material = playerMaterialClone;
              RendererTankRight.material = playerMaterialClone;
          }
      
          // OnStartLocalPlayer 类似star(),只在本地玩家上被调用
          public override void OnStartLocalPlayer()
          {
              // 获取玩家名字
              string name = PlayerPrefs.GetString("playerName");
              // 获取颜色一个颜色
              Color tankColor = new Color(PlayerPrefs.GetFloat("tankR"), PlayerPrefs.GetFloat("tankG"), PlayerPrefs.GetFloat("tankB"));
              // 调用方法,修改变量要在服务器端
              cmdSetupPlayer(name, tankColor);
      
              // 与相机的偏移量,相机 的初始角度
              rb = GetComponent<Rigidbody>();
              offset = Camera.main.transform.position - transform.position;
              camRotation = Camera.main.transform.rotation;
          }
      
      
          // 物理动作尽量放到FixedUpdate中
          void FixedUpdate()
          {
              // 如果不是本地玩家,不执行下面的代码
              if (!isLocalPlayer) return;
              // 相机跟随
              Camera.main.transform.position = transform.position + offset;
              // 控制转向--转向是y轴方向转向,水平变量控制
              float h = Input.GetAxis("Horizontal");
              // 控制前进
              float v = Input.GetAxis("Vertical");
              // 前进
              rb.velocity = transform.forward * v * moveSpeed;
              // 转向
              rb.angularVelocity = transform.up * h * turnSpped;
      
              // 如果按下空格键,射击
              if (Input.GetKeyDown(KeyCode.Space))
              {
                  // 射击
                  shoot();
              }
          }
      
          //加上[Command]的方法,为客户端向服务器端发消息,让服务器执行此方法
          [Command]
          void shoot()
          {
              // 实例化子弹
              GameObject go = Instantiate(shellPrefab, firePoint.position, firePoint.rotation);
              // Spawn:在所有的客户端生成括号里面的物体
              NetworkServer.Spawn(go);
          }
      
      
      
      
          //Command,从客户端调用,但是在服务器上运行
          [Command]
          void cmdSetupPlayer(string _name, Color _color)
          {
              //玩家信息发送到服务器,然后服务器更新所有客户端上SyncVar变量
              playerName = _name;
              playerColor = _color;
          }
      }
    • 现在能够同步出现子弹了,但是子弹没有速度,创建脚本ShellControl.cs脚本,拖拽给shell子弹,将爆炸效果拖拽上去
    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class ShellControl : MonoBehaviour
      {
          public float shellSpeed = 10;    // 子弹速度
          private Rigidbody shellRb;       // 子弹刚体组件
      
          public GameObject shellExplosionPrefab; // 子弹爆炸效果
          // Start is called before the first frame update
          void Start()
          {
              shellRb = GetComponent<Rigidbody>();
              // 设置初速度,不能写到update中,速度只设置一次就好
              shellRb.velocity = transform.forward * shellSpeed;
          }
      
      
          // 子弹碰撞到东西是爆炸
          private void OnTriggerEnter(Collider other)
          {
              // 实例化爆炸效果
              GameObject.Instantiate(shellExplosionPrefab, transform.position, transform.rotation);
              // 删除子弹
              Destroy(gameObject);
          }
      }
    • 由于每个客户端都生成了子弹,所有爆炸效果也都可以生成,所以不需要考虑爆炸同步,子弹能够删除,但是爆炸效果没有删除,所以为爆炸效果创建组件DestoryExplore.cs

    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class DestoryExplore : MonoBehaviour
      {
          // Start is called before the first frame update
          void Start()
          {
              // 删除爆炸效果
              GameObject.Destroy(gameObject, 1.5f);
          }
      
      }
    • 发射子弹功能完毕(爆炸效果还是有点问题
  • 6、坦克伤害
    • 创建变量hp表示坦克血量,判断坦克的碰撞体,碰到的是不是子弹,谁碰到,谁减血,用到TargetRpc。
    • 打包,运行,会发现只有客户端的 删掉了自己的坦克,因为我们只让受伤的目标客户端删掉了(看天蓝色的坦克)。修改代码

    •  

       

    • 现在就可以了

  • 7、坦克重生

    • 坦克消失,身上的脚本就不起作用了,所以需要在场景中创建一个空物体,重命名GameManager,在上面创建脚本GameManager.cs,编写代码

    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using UnityEngine.SceneManagement;
      using Mirror;
      
      public class GameManager : NetworkBehaviour
      {
          // 为了在其他脚本中,更好的调用,制作成单例
          public static GameManager instance;
          // 判断玩家是否死亡
          public bool isPlayerDead = false;
      
          private void Awake()
          {
              instance = this;
          }
      
          private void Update()
          {
              // 如果按下p键并且isPlayerDead为True,才能重生
              if (Input.GetKeyDown(KeyCode.P) && isPlayerDead )
              {
                  // 重新设置摄像机的位置
                  Camera.main.transform.position = new Vector3(0f, 3.5f, -9.5f);
                  // 重新生成玩家
                  NetworkClient.AddPlayer();
              }      
          }
      }
    • 在TankControl脚本中,添加代码
    • 坦克重生完成,运行会发现,重生的坦克有些问题(会和最后一个进入客户端的坦克同名同材质,)稍后测试是不是同一台电脑的原因
    • 目前为止,此时代码TankControl.cs的代码
    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      // 引用mirror
      using Mirror;  
      // 继承NetworkBehaviour
      public class TankControl : NetworkBehaviour
      {
          // 同步姓名、材质
          // 3DText,用于显示玩家姓名
          public TextMesh player3DText;
          
          // 坦克盖材质
          public MeshRenderer RendererTank;
          // 坦克头材质
          public MeshRenderer RendererTankHead;
          // 坦克左轮
          public MeshRenderer RendererTankLeft;
          // 坦克右轮
          public MeshRenderer RendererTankRight;
          // 存放
          private Material playerMaterialClone;
      
      
      
          // 相机跟随+移动
          private float moveSpeed = 5;  // 坦克移动的速度
          private float turnSpped = 5; // 坦克转向的速度
          private Rigidbody rb;    // 刚体组件
      
          private Vector3 offset;//和相机的相对位置
          Quaternion camRotation;   //记录相机初始角度
      
      
          // 发射子弹
          public GameObject shellPrefab;   // 子弹预制体
          public Transform firePoint;     // 发射点的Transform组件
      
      
          // 坦克血量
          int hp = 20;
      
      
      
          // SynVar用于同步服务器和所有客户端的变量,变量只能在服务器上更改
          // hook允许你创建一个在客户端的方法,当客户端上接受到更新的信息后,执行这个方法
          [SyncVar(hook = nameof(OnNameChanged))]
          public string playerName;
          // 名字发生改变
          void OnNameChanged(string _old, string _new)
          {
              player3DText.text = playerName;
          }
      
      
          [SyncVar(hook = nameof(OnColorChanged))]
          public Color playerColor;
      
          // 颜色发生变化TODO
          void OnColorChanged(Color _old, Color _new)
          {
              // 为playerMaterialClone赋值,任何都可以,只要是material类型
              playerMaterialClone = new Material(RendererTank.material);
              playerMaterialClone.color = playerColor;
              RendererTank.material = playerMaterialClone;
              RendererTankHead.material = playerMaterialClone;
              RendererTankLeft.material = playerMaterialClone;
              RendererTankRight.material = playerMaterialClone;
          }
      
          // OnStartLocalPlayer 类似star(),只在本地玩家上被调用
          public override void OnStartLocalPlayer()
          {
              // 获取玩家名字
              string name = PlayerPrefs.GetString("playerName");
              // 获取颜色一个颜色
              Color tankColor = new Color(PlayerPrefs.GetFloat("tankR"), PlayerPrefs.GetFloat("tankG"), PlayerPrefs.GetFloat("tankB"));
              // 调用方法,修改变量要在服务器端
              cmdSetupPlayer(name, tankColor);
      
              // 与相机的偏移量,相机 的初始角度
              rb = GetComponent<Rigidbody>();
              offset = Camera.main.transform.position - transform.position;
              camRotation = Camera.main.transform.rotation;
          }
      
      
          // 物理动作尽量放到FixedUpdate中
          void FixedUpdate()
          {
              // 如果不是本地玩家,不执行下面的代码
              if (!isLocalPlayer) return;
              // 相机跟随
              Camera.main.transform.position = transform.position + offset;
              // 控制转向--转向是y轴方向转向,水平变量控制
              float h = Input.GetAxis("Horizontal");
              // 控制前进
              float v = Input.GetAxis("Vertical");
              // 前进
              rb.velocity = transform.forward * v * moveSpeed;
              // 转向
              rb.angularVelocity = transform.up * h * turnSpped;
      
              // 如果按下空格键,射击
              if (Input.GetKeyDown(KeyCode.Space))
              {
                  // 射击
                  shoot();
              }
          }
      
          //加上[Command]的方法,为客户端向服务器端发消息,让服务器执行此方法
          [Command]
          void shoot()
          {
              // 实例化子弹
              GameObject go = Instantiate(shellPrefab, firePoint.position, firePoint.rotation);
              // Spawn:在所有的客户端生成括号里面的物体
              NetworkServer.Spawn(go);
          }
      
      
      
          //Command,从客户端调用,但是在服务器上运行
          [Command]
          void cmdSetupPlayer(string _name, Color _color)
          {
              //玩家信息发送到服务器,然后服务器更新所有客户端上SyncVar变量
              playerName = _name;
              playerColor = _color;
          }
      
          // 进入的是子弹
          private void OnCollisionEnter(Collision collision)
          {
              // 这个判断需要在服务器端调用,不能是客户端,如果发现卡顿,就会出现问题
              if(collision.gameObject.tag == "Shell" && isServer)
              {
                  // 谁碰到谁减血
                  NetworkIdentity networkIdentity = GetComponent<NetworkIdentity>();
                  TakeDamage(networkIdentity.connectionToClient);
              }
          }
      
          [TargetRpc]
          void TakeDamage(NetworkConnection connection)
          {
              
              if (hp <= 0) return;  // 如果血量小于0,直接返回
      
              hp -= 10; // 伤害
              //第一次 血量变成0
              if (hp <= 0)
              {
                  // 将目标客户端上的isPlayerDead,设置为True
                  GameManager.instance.isPlayerDead = true;
                  // 删除这个坦克,在服务器上删除,其他的客户端都会删
                  DestroyTank();
              }
          }
          
          [Command]
          void DestroyTank()
          {
              NetworkServer.Destroy(this.gameObject);
          }
      }
  • 8、答题重生
    • 创建一个脚本QuestionData.cs,里面写一个题类
    • // 题类
      public class QuestionData 
      {
          // 问题
          public  string question;
          // 答案
          public string answer;
      }
    • 创建一个文件夹Resources,在创建一个文本,里面题和答案用逗号分割开
    • 修改GameManager.cs中的代码
    • 创建UI,自己做选择,创建一个Text用来显示题目,一个输入框输入答案,一个按钮用来确定,我还创建了一个Text用来提示(按p键重生)
    •  编写代码

    • using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using UnityEngine.SceneManagement;
      using Mirror;
      using UnityEngine.UI;
      
      public class GameManager : NetworkBehaviour
      {
          // 为了在其他脚本中,更好的调用,制作成单例
          public static GameManager instance;
          // 判断玩家是否死亡
          public bool isPlayerDead = false;
          // 创建列表,存储每一道题
          List<QuestionData> quests = new List<QuestionData>();
      
          // 显示题目的面板
          public GameObject panel;
          // 答案
          public Text answer;
          // 问题
          public Text question;
          // 题目序号
          int n = 0;
      
          private void Awake()
          {
              instance = this;
              // 加载问题文件
              TextAsset questiondata = Resources.Load<TextAsset>("question1");
              string a = questiondata.text;
              foreach (var item in a.Split('\n'))
              {
                  QuestionData q = new QuestionData();
                  string[] b = item.Split(',');
                  q.question = b[0];
      
                  // 大坑:1、编码格式不同 2、没有清除前后的空格
                  q.answer = b[1].Trim();
                  quests.Add(q);
              }
              // 设置初始问题
              question.text = quests[n].question;
          }
      
          private void Update()
          {
              // 如果按下p键并且isPlayerDead为True,才能重生
              if (Input.GetKeyDown(KeyCode.P) && isPlayerDead )
              {
                  // 显示问题面板
                  panel.SetActive(true);
              }      
          }
      
          public void Check()
          {
              // 如果回答对
              if (answer.text == quests[n].answer)
              {
                  // 随机一个序号
                  n = Random.Range(0, quests.Count - 1);
                  question.text = quests[n].question;
                  // 隐藏问题面板
                  panel.SetActive(false);
                  // 设置isPlayerDead为false
                  isPlayerDead = false;
                  // 重新设置摄像机的位置
                  Camera.main.transform.position = new Vector3(0f, 3.5f, -9.5f);
                  // 重新生成玩家
                  NetworkClient.AddPlayer();
      
              }
          }
      }
    • 拖拽需要的变量
    • 结束

  • 之后是微调,比如添加点错误提示,加点音效,扩展题库。 

 

posted on 2022-01-07 16:58  酱紫安  阅读(367)  评论(0编辑  收藏  举报

导航