这一章我们来设计基础的枪械功能。
枪械的开火
我现在选择的武器是416,所以数据会是416的一些数据。
开火流程
第一步,要找到枪口。我们之前已经做了瞄准点了,现在只需要让子弹从枪口出现,然后飞向瞄准点。
自然,这个射击逻辑由各位的想法而定。无论是射线射击,或者说实体子弹;无论是从瞄准镜飞出,还是从枪口飞出;无论是以瞄准点为方向,还是以枪口实际朝向作为飞行方向都可以。
开火前的准备
由于我们是从枪口生成实体子弹,所以需要准备子弹实体以及枪口位置。
实体子弹的准备
首先,我们的子弹虽然是实体的,但我们不需要其有模型。我们只希望由可视化的弹道。所以我们的子弹设计是这样子的:
1.由碰撞体与刚体以保证能与其他物体发生碰撞;
2.有弹道。
我们这里主要聊一下弹道。在Unity中有一个TrailRenderer(拖尾渲染器)。我们为子弹添加上这个组件。
这个红色曲线代表我们的拖尾在生存周期中的宽度变化。下边还有诸如颜色的变化,生存时间,材质等。
我们的拖尾在编辑器中拉动一下发现是粉红色的,这代表其未添加材质。我们为其创建一个。创建一个材质,Shader选择Legacy Shaders/Particles/Additive,这个材质代表了其属性是粒子效果。设置一下颜色
好,接下来为拖尾添加上这个材质
我们把它做成一个预制体。枪口就是简单的设置一个空物体挂载在枪口就可以了。
开火逻辑
这段代码我挂在了枪上。
我们首先获取属性
[Header("Firing")]
[Tooltip("是否为全自动")]
[SerializeField]
private bool automatic;
[Tooltip("子弹飞行速度")]
[SerializeField]
private float _ammoSpeed;
[Header("BaseSettings")]
[Tooltip("枪口")]
[SerializeField]
private Transform muzzle;
[Tooltip("瞄准点")]
[SerializeField]
private Transform targetpoint;
[Tooltip("子弹")]
[SerializeField]
private GameObject _ammo;
private PlayerInputsMassage _inputMessage;
[Tooltip("子弹的对象池")]
private ObjectPool<GameObject> ammoPool;
这里我考虑使用对象池来管理子弹。
对象池目前仍然有很多问题,大家可以使用普通的方法处理。对象池中会出现的问题是弹道显示,这个问题我没有找到解决方法之前,我并不推荐大家使用对象池
那么在Start中建立对象池。在建立对象池前,要先为子弹设计一个类去做碰撞相关的事情。
在这里我们设计了一个事件作为碰撞的事件。然后在Gun中就可以添加相应的方法让子弹执行。
public UnityEvent destroyEvent = new UnityEvent();
public bool isDestroy;
private void OnEnable()
{
isDestroy = false;
}
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Gun") && !isDestroy)
{
isDestroy = true;
destroyEvent?.Invoke();
}
}
_inputMessage = FindObjectOfType<PlayerInputsMassage>();
ammoPool = new ObjectPool<GameObject>(
() =>
{
var ammo = Instantiate(_ammo, muzzle);
ammo.AddComponent<AmmoBehavier>().destroyEvent.AddListener(() =>
{
ammoPool.Release(ammo);
}
);
return ammo;
},
(go) =>
{
go.SetActive(true);
go.transform.position = muzzle.position;
},
(go) =>
{
go.SetActive(false);
},
(go) => {
Destroy(go); }
);
接下来我们写一个Fire方法。
这里我们用对象池的get代替了平时的生成。生成则是交由对象池处理。
public void Fire()
{
Quaternion rotation = Quaternion.LookRotation(targetpoint.position - muzzle.position);
GameObject ammo = ammoPool.Get();
ammo.transform.rotation = rotation;
ammo.GetComponent<Rigidbody>().velocity = ammo.transform.forward * _ammoSpeed;
}
接下来我们设置一个非常好用的东西:EventHandler
可以认为这是一个专门处理事件的类,我们会将许多跨脚本的代码用它来写,会很方便调用。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public static class EventHandler
{
public static event Action WeaponFire;
public static void CallWeaponFire()
{
WeaponFire?.Invoke();
}
}
这样开火的事件就写好了,我们只需要把每个代码需要处理与开火相关的内容写成一个方法,然后添加到这个事件中,然后由开火键触发这个方法即可。我们接下来简单演示一下(注意,这里我们的开火方法与触发都写在了Gun中,但以后涉及到枪械切换,就会将其分离)
private void OnEnable()
{
EventHandler.WeaponFire += Fire;//在该脚本启用时添加该方法
}
private void OnDisable()
{
EventHandler.WeaponFire -= Fire;//在该脚本停用时撤销该方法
}
这样就做到了方法的注册,接下来是调用,该调用是可以放在任何地方,我只是希望将人物的移动与射击分离才这样写的。否则人物的控制脚本将会十分臃肿。
private void Update()
{
if(_inputMessage.fire&&_inputMessage.aim)
{
EventHandler.CallWeaponFire();
}
}
这一章的内容暂时先到这里,由于有些问题仍然没有得到解决,这一章可能后续修改的可能性比较大。