实现武器单发和连发

  1. 在PlayerWeapon类中定义武器的shootRate,用于表示射速。

  2. 如果shootRate这个值为0的话,说明是单发,否则的话通过shootRate来实现每秒打多少发。连发实际上就是快速的单发。通过InvokeRepeating这个函数可以实现这个功能(周期性地调动这个函数,第一个参数:多少秒后第一次调用; 第二个参数:平均每两次的执行间隔)

  3. 当我们松开的时候,需要将这个事件终止。同样的也有一个api——CancelInvoke(“函数名称”)

void Update()
{
    if (!IsLocalPlayer) return;
    currentWeapon = weaponManager.GetCurrentWeapon();
    if (currentWeapon.shootRate <= 0)
    {
        if (Input.GetButtonDown("Fire1"))
        {
            Shoot();
        }
    }
    else
    {
        if (Input.GetButtonDown("Fire1"))
        {
            InvokeRepeating("Shoot", 0f, 1f / 10);
        }
        else if (Input.GetButtonUp("Fire1") || Input.GetKey(KeyCode.Q))
        {
            CancelInvoke("Shoot");
        }
    }
}

实现人物切换武器

为了方便武器的更换,在这里我们采取一种新的方式来动态更换武器

  1. 首先,我们要知道,人物拿着武器,实质上就是将武器放在一个合适的位置,然后再将这个武器给渲染出来。因此,在这里我们可以首先在武器的位置创建一个空的GameObject来记录当前武器的位置,在Player预制体中的Camera下,创建一个GameObject,命名为WeaponHolder,我们只需要动态的将武器挂载到WeaponHolder中即可。

  2. 其次,我们来创建一个脚本用来管理玩家的武器——WeaponManager.cs。由于我们的武器之前是挂载在PlayerWeapon上的,这个脚本上包含着武器的信息,我们还需要添加一些变量shootRate(一秒可以打多少发)。

  3. 第三步,我们需要写几个辅助函数,例如获取当前的武器,装备武器、更改武器等。

  4. 在Update()中监听切换武器的快捷键Q。

  5. 处理网络同步问题,和之前讲到的一样,写一个ServerRpc函数,用于在服务器端执行,写一个ClientRpc函数,用于在客户端执行。从而实现客户端和服务端同步,其他玩家也可以看到切枪。

  6. 最后更改PlayerShoot.cs 文件,将其中weapon属性改为currentWeapon,且在Update()中动态地从WeaponManager类中获取当前武器。

具体代码

weaponManager.cs

using Unity.Netcode;
using UnityEngine;

public class WeaponManager : NetworkBehaviour
{
    [SerializeField]
    private GameObject weaponHolder;

    [SerializeField]
    private PlayerWeapon primaryWeapon;

    [SerializeField]
    private PlayerWeapon secondaryWeapon;

    private PlayerWeapon currentWeapon;

    private WeaponGraphics currentGraphics;

    void Start()
    {
        EquipWeapon(primaryWeapon);
    }

    public PlayerWeapon GetCurrentWeapon()
    {
        return currentWeapon;
    }

    public WeaponGraphics GetCurrentGraphics()
    {
        return currentGraphics;
    }

    private void EquipWeapon(PlayerWeapon weapon)
    {
        currentWeapon = weapon;


        for (int i = 0; i < weaponHolder.transform.childCount; i++)
        {
            Destroy(weaponHolder.transform.GetChild(i).gameObject);
        }

        GameObject weaponObject = Instantiate(currentWeapon.graphics, weaponHolder.transform.position, weaponHolder.transform.rotation);
        weaponObject.transform.SetParent(weaponHolder.transform);

        currentGraphics = weaponObject.GetComponent<WeaponGraphics>();
    }

    public void ToggleWeapon()
    {
        if (currentWeapon == primaryWeapon)
        {
            EquipWeapon(secondaryWeapon);
        }
        else
        {
            EquipWeapon(primaryWeapon);
        }
    }

    [ClientRpc]
    private void ToggleWeaponClientRpc()
    {
        ToggleWeapon();
    }

    [ServerRpc]
    private void ToggleWeaponServerRpc()
    {
        if (!IsHost)
        {
            ToggleWeapon();
        }
        ToggleWeaponClientRpc();
    }

    void Update()
    {
        if (IsLocalPlayer)
        {
            if (Input.GetKeyDown(KeyCode.Q))
            {
                ToggleWeaponServerRpc();
            }
        }
    }
}

PlayerShoot.cs

using Unity.Netcode;
using UnityEngine;

public class PlayerShoot : NetworkBehaviour
{
    private const string PLAYER_TAG = "Player";

    private PlayerWeapon currentWeapon;

    private Camera eyesCamera;

    private WeaponManager weaponManager;

    private PlayerController playerController;

    void Start()
    {
        eyesCamera = GetComponentInChildren<Camera>();
        weaponManager = GetComponent<WeaponManager>();

        playerController = GetComponent<PlayerController>();
    }

    [ServerRpc]
    private void OnShootServerRpc()
    {
        if (!IsHost)
        {
            OnShoot();
        }
        OnShootClientRpc();
    }

    [ClientRpc]
    private void OnShootClientRpc()
    {
        OnShoot();
    }

    private void Shoot()
    {
        RaycastHit hit;
        if (Physics.Raycast(eyesCamera.transform.position, eyesCamera.transform.forward, out hit, currentWeapon.range))
        {
            if (hit.collider.tag == PLAYER_TAG)
            {
                ShootServerRpc();
            }
        }
    }

    [ServerRpc]
    private void ShootServerRpc(string name, int damage)
    {
        PlayerObject player = GameManager.singleton.GetPlayer(name);
        player.TakeDamage(damage);
    }

    void Update()
    {
        if (!IsLocalPlayer) return;
        currentWeapon = weaponManager.GetCurrentWeapon();
    }
}