切换武器
给角色加一把手枪,并能在步枪和手枪之间切换
枪械独立化
人枪分离
为了实现多个武器的切换,给 Player
添加一个空物体武器槽 WeaponHolder
将枪的坐标复制到 WeaponHolder
上
- 右键武器物体的
Transform
,在Copy
中选择Position(World)
,这个选项是复制该物体的所有Transform
信息,包括位置、旋转和缩放 - 右键
WeaponHolder
的Transform
,选择Paste
里的Postion(world)
,成功粘贴
将枪的 Transform
重置,拖进 Prefabs
文件夹中,然后将枪删除。枪械未来会挂载在 WeaponHolder
中。拖进 Prefabs
时会出现一个弹窗,选择 Original Prefab
渲染枪械
在 Player
上创建脚本 WeaponManager.cs
。WeaponManager
控制武器渲染,需要进行网络通信,因此继承的类改成 NetworkBehaviour
实现主武器
- 武器的类是
PlayerWeapon
。新建变量来存储主武器和角色当前武器 - 新建函数
EquipWeapon()
来搭载武器。初始化时默认玩家使用主武器 - 新建函数
GetCurrentWeapon
获取当前武器
打开 PlayerShooting
. 在 PlayerShooting
中我们维护了武器信息:
[SerializeField]
private PlayerWeapon weapon;
现在武器信息由 WeaponManager
维护,因此可以删去这段代码,只使用一个角色当前装备武器的变量,和一个 WeaponManager
的引用。这里使用 GetComponent
获取 WeaponManager
每次在射击之前,先获取当前武器,将先前的 weapon
变量变为 currentWeapon
回到 PlayerWeapon
中,为了将分隔后的枪渲染出来,PlayerWeapon
需要和枪的物体绑定,建立一个 GameObject
变量来绑定枪,然后在unity编辑器中将枪拖进该变量中
渲染主武器
在 WeaponManager
中编写渲染武器的代码,武器在装备上时渲染。武器要渲染到 WeaponHolder
中,因此要用一个变量取到 WeaponHolder
的引用。这里使用 SerializeField
从编辑器中手动取到引用
要在 WeaponHolder
中渲染出武器,可以用如下代码
// 创建渲染一个武器物体对象
GameObject weaponObject = Instantiate(currentWeapon.graphics, weaponHolder.transform.position, weaponHolder.transform.rotation);
weaponObject.transform.SetParent(weaponHolder.transform); // 将武器对象挂载到WeaponHolder上
实现连发
在 PlayerWeapon
中添加一个变量表示武器每秒射击频率,小于等于0表示单发,大于0表示连发。
在 PlayerShooting
中,添加连发逻辑。连发时在用户按下鼠标左键后,每秒钟按一定时间间隔周期性调用 Shoot()
函数,直到检测到用户松开鼠标左键后停止。
if (Input.GetButtonDown("Fire1"))
{
InvokeRepeating("Shoot", 0f, 1f / currentWeapon.shootRate);
}
else if (Input.GetButtonUp("Fire1"))
{
CancelInvoke("Shoot");
}
实现副武器
在Low Poly Guns 中选择一把手枪拖进场景中,加上颜色。记得把枪的坐标重置,然后拖进 Prefabs
文件夹中。
在 WeaponManager
中创建一个变量表示副武器。然后在unity编辑器中将武器的信息进行修改
切枪
在切枪时要判断是否是本地玩家,只切换本地玩家的枪。切枪按键设置为Q。编写一个切枪函数来切枪。
private void ToggleWeapon()
{
if (currentWeapon == primaryWeapon)
{
EquipWeapon(secondaryWeapon);
}
else
{
EquipWeapon(primaryWeapon);
}
}
注意,切枪时要把先前搭载的武器物体删除。在装备武器的函数里添加如下代码:
if (weaponHolder.transform.childCount > 0)
{
Destroy(weaponHolder.transform.GetChild(0).gameObject); // 去掉先前挂载的枪的gameObject,Destroy不能套在while里,容易死循环
}
切枪联机同步
在 WeaponManager
中编写在服务器上执行切枪函数,要加上 ServerRpc
注解和后缀。该函数先在服务器上调用切枪函数将枪切好,然后调用 ClientRpc
的切枪函数将其他客户端的枪切好
[ClientRpc]
private void ToggleWeaponClientRpc()
{
ToggleWeapon();
}
[ServerRpc]
private void ToggleWeaponServerRpc()
{
if (!IsHost) // 这里判断是不是Host,如果不判断,那Host会执行两次切枪,导致主武器和副武器同时出现
{
ToggleWeapon();
}
ToggleWeaponClientRpc();
}
修改BUG
此时切枪联机做好了,但有一个BUG,就是当玩家在对局里切好枪之后,后加入对局的玩家看到切枪玩家拿的枪依然是没有切换的。这个BUG的出现是因为玩家持有的枪并没有记录在服务器中,服务器只会执行切枪的行为。因此我们需要一个网络变量来记录玩家当前的枪。
设置网络变量 private NetworkVariable<int> currentWeaponIndex = new NetworkVariable<int>();
记录玩家当前持的枪,初始值为0
将 Start()
改为 OnNetworkSpawn()
, 因为 WeaponManager
是一个网络变量。
装备武器是传入的参数改为一个整数,通过整数数值来判断当前应该装备哪种武器
切枪时,ToggleWeaponServerRpc()
函数中
[ServerRpc]
private void ToggleWeaponServerRpc()
{
currentWeaponIndex.Value = (currentWeaponIndex.Value + 1) % 2; // 改变数值来切枪,只有两把枪
if (!IsHost)
{
ToggleWeapon(currentWeaponIndex.Value); // 这里一定要传入数值,因为客户端和服务器端的网络变量数值不一定同步,会出现切枪错误的问题,所以这里直接传修改好的值
}
ToggleWeaponClientRpc(currentWeaponIndex.Value); // 同理
}
其他代码段依次修改,详情见下。这时就解决了BUG。
阶段性成品
修改的代码
PlayerShooting.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
public class PlayerShooting : NetworkBehaviour
{
private const string PLAYER_TAG = "Player";
private WeaponManager weaponManager;
private PlayerWeapon currentWeapon;
[SerializeField]
private LayerMask mask;
private Camera cam; // 角色摄像头
// Start is called before the first frame update
void Start()
{
cam = GetComponentInChildren<Camera>();
weaponManager = GetComponent<WeaponManager>();
}
// Update is called once per frame
void Update()
{
currentWeapon = weaponManager.GetCurrentWeapon();
if (currentWeapon.shootRate <= 0) // 单发
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
else
{
if (Input.GetButtonDown("Fire1"))
{
InvokeRepeating("Shoot", 0f, 1f / currentWeapon.shootRate);
}
else if (Input.GetButtonUp("Fire1"))
{
CancelInvoke("Shoot");
}
}
}
private void Shoot()
{
Debug.Log("Shoot!!!!");
RaycastHit hit;
if (Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, currentWeapon.range, mask))
{
if (hit.collider.tag == PLAYER_TAG)
{
ShootServerRpc(hit.collider.name, currentWeapon.damage);
}
}
}
[ServerRpc]
private void ShootServerRpc(string name, int damage)
{
Player player = GameManager.Singleton.GetPlayer(name);
player.TakeDamage(damage);
}
}
PlayerWeapon.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class PlayerWeapon
{
public string name = "AUGA3";
public int damage = 10;
public float range = 100f;
public float shootRate = 10f; // 一秒可以打多少发子弹。如果小于等于0,则为单发
public GameObject graphics;
}
WeaponManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
public class WeaponManager : NetworkBehaviour
{
[SerializeField]
private PlayerWeapon primaryWeapon;
[SerializeField]
private PlayerWeapon secondaryWeapon;
private NetworkVariable<int> currentWeaponIndex = new NetworkVariable<int>(); // 记录玩家当前持的枪,初始值为0
[SerializeField]
private GameObject weaponHolder;
private PlayerWeapon currentWeapon;
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
EquipWeapon(currentWeaponIndex.Value);
}
public void EquipWeapon(int weaponIndex)
{
if (weaponIndex == 0)
{
currentWeapon = primaryWeapon;
}
else if (weaponIndex == 1)
{
currentWeapon = secondaryWeapon;
}
if (weaponHolder.transform.childCount > 0)
{
Destroy(weaponHolder.transform.GetChild(0).gameObject); // 去掉先前挂载的枪的gameObject
}
// 创建渲染一个武器物体对象
GameObject weaponObject = Instantiate(currentWeapon.graphics, weaponHolder.transform.position, weaponHolder.transform.rotation);
weaponObject.transform.SetParent(weaponHolder.transform); // 将武器对象挂载到WeaponHolder上
}
public PlayerWeapon GetCurrentWeapon()
{
return currentWeapon;
}
private void ToggleWeapon(int weaponIndex)
{
EquipWeapon(weaponIndex);
}
[ClientRpc]
private void ToggleWeaponClientRpc(int weaponIndex)
{
ToggleWeapon(weaponIndex);
}
[ServerRpc]
private void ToggleWeaponServerRpc()
{
currentWeaponIndex.Value = (currentWeaponIndex.Value + 1) % 2; // 改变数值来切枪,只有两把枪
if (!IsHost)
{
ToggleWeapon(currentWeaponIndex.Value); // 这里一定要传入数值,因为客户端和服务器端的网络变量数值不一定同步,会出现切枪错误的问题,所以这里直接传修改好的值
}
ToggleWeaponClientRpc(currentWeaponIndex.Value); // 同理
}
public void SetDefaultWeapon()
{
currentWeaponIndex.Value = 0;
}
// Update is called once per frame
void Update()
{
if (IsLocalPlayer)
{
if (Input.GetKeyDown(KeyCode.Q)) // 按下Q切换武器
{
ToggleWeaponServerRpc();
}
}
}
}
著作权信息
- 源视频作者:yxc
- 文章作者: jaylenwanghitsz
- 链接:https://www.acwing.com/file_system/file/content/whole/index/content/8434760/
- 来源:AcWing
- 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。