功能
- 按3如果没在用手榴弹,则拿出手榴弹。如果在用,则切换下一种手榴弹。没有则空手;
- 只有投掷准备状态点击投掷。按下鼠标后直到拿出下一个手榴弹回到投掷准备状态时不响应点击;
数据
- 新增手榴弹配表数据类,新建手榴弹配表,配表管理器新增手榴弹配表字典,初始化增加读取配表并转换成字典的代码,增加根据id得到手榴弹详情的方法;
- 手榴弹的预制体需要可拾取的、扔出的;
- 背包界面增加背包内的手榴弹、场景里的手榴弹、别人背包的手榴弹格子预制体和类;
- 人物有一个int grenadeIndex记录当前拿着的手榴弹在背包手榴弹列表的索引,没拿手榴弹时是-1;
- 每种手榴弹有一个待拾取预制体和一个生效预制体。待拾取预制体有一个手榴弹模型、一个触发器用来被玩家检测到,一个数据脚本记录自己是哪种手榴弹,没有投掷、爆炸相关功能。生效预制体有刚体碰撞体管理运动,投掷、爆炸相关功能。
用枚举记录手榴弹类型
public enum GrenadeType{
Frag,Smoke,Flash
}
手榴弹详细数据,这里不管类型,所有需要的数据都写在这里
[Serializable]
public class GrenadeDataBin:ItemDataBin{
public GrenadeType grenadeType;
public Grenade prefabThrowed;
public ParticleSystem explosionEffect;
public AudioClip explodeAudio;
public float explodeDelay=3;
public float lifeTimeAfterExplode;
public float explodeRadius=2;
public int damage;
}
逻辑
拾取和使用
- 在交互系统的拾取物品分支增加对手榴弹类的判断;
- 按拿出手榴弹的按键时
public void GetThrowInput(InputAction.CallbackContext input){ if(input.ReadValue<float>()==0){ switch(player.animator.GetInteger(CharacterBase.gunStatusPara)){ case CharacterBase.noWeaponState: case CharacterBase.rifleState: case CharacterBase.handgunState: player.UseGrenade(); break; case 3: player.PutAwayGun(); break; } } } - GetNextGrenade():在背包的手榴弹列表遍历,有和grenadeIndex类型不同的手榴弹则返回索引,没有则返回-1;
- UseGrenade(),先执行grenadeIndex=GetNextGrenade(),在右手实例化手榴弹,没有则回到空手;
- 扔手榴弹方法通过动画事件执行
using UnityEngine;
namespace Mercenaria
{
/// <summary>
/// 人物类.手榴弹模块
/// </summary>
public partial class CharacterBase
{
public OtherItemInPack grenadeInHandData = null;
Grenade grenadeInHand;
[SerializeField]
float throwSpeed = 20;
[SerializeField]
bool canThrow;
/// <summary>
/// 尝试使用手榴弹。如果有传入的手榴弹数据,直接用(背包界面直接指定使用某个手榴弹)。
/// 没有传入的,则寻找手榴弹,如果找到,
/// 人物进入投掷状态。
/// </summary>
/// <returns></returns>
public virtual bool UseGrenade(OtherItemInPack item)
{
grenadeInHandData =item==null? GetNextGrenade():item;
if (grenadeInHandData == null)
{//没拿到
canThrow = false;
return false;
}
animator.SetInteger(gunStatusPara, grenadeState);
ShowGrenade();
canThrow = true;
return true;
}
/// <summary>
/// 在人物手里生成手榴弹。不放在UseGrenade,因为人物
/// 可能要先收起枪,进入投弹状态时再动画事件执行这个。
/// 还是放到UseGrenade了,因为准备投掷状态是个循环动画,使用
/// 动画事件会方法执行
/// </summary>
public void ShowGrenade()
{
GrenadeDataBin grenadeDataBin = MyConfigManager.Instance.GetOtherItemFromSO(
grenadeInHandData.type, grenadeInHandData.id) as GrenadeDataBin;
if (grenadeInHand)
{
Destroy(grenadeInHand.gameObject);
}
grenadeInHand = Instantiate(grenadeDataBin.prefabThrowed, weaponHolder).GetComponent<Grenade>();
grenadeInHand.grenadeData = grenadeDataBin;
grenadeInHand.owner = this;
}
/// <summary>
/// 去背包寻找手榴弹
/// </summary>
/// <returns></returns>
OtherItemInPack GetNextGrenade()
{
if (grenadeInHandData == null)
{//手里没有手榴弹,使用找到的第一种
for (int i = 0; i < pack.otherItemsInPack.Count; i++)
{
if (pack.otherItemsInPack[i].type == Props_Type.Grenade)
{
return pack.otherItemsInPack[i];
}
}
return null;//背包里也没有,不拿
}
for (int i = 0; i < pack.otherItemsInPack.Count; i++)
{//手里有手榴弹,切换下一种
if (pack.otherItemsInPack[i].type == Props_Type.Grenade)
{//是手榴弹
if (pack.otherItemsInPack[i] != grenadeInHandData)
{//类型和手里的不同
return pack.otherItemsInPack[i];
}
}
}
return null;
}
public virtual void ThrowGrenadeEvent()
{
grenadeInHand.Throwed(aimAxis.forward * throwSpeed);
pack.TakeOutOtherItem(grenadeInHandData);
grenadeInHand = null;
grenadeInHandData = null;
//没手榴弹了,回到空手
if (!UseGrenade(null))
{
PutAwayWeapon();
}
}
/// <summary>
/// 播放拉环、准备投出动画
/// </summary>
void PullGrenadeRing()
{
if (canThrow)
{
canThrow = false;
animator.SetTrigger(PullRing);
MyAudioManager.Single.MyPlaySound(audioSource,
MyConfigManager.Instance.mainConfigSO.audioThrow);
}
}
/// <summary>
/// 播放投出动画
/// </summary>
void ThrowOut()
{
animator.SetTrigger(throwPara);
}
}
}
物理
手榴弹只和地面层碰撞,不能碰撞人物,否则干扰扔出手榴弹的弹道。
爆炸效果
扔出一段时间后爆炸,生成粒子效果,播放爆炸声音, 粒子效果播放完后销毁。
public void Throwed(Vector3 velocity){
transform.SetParent(null);
_rigidbody.isKinematic=false;
_collider.isTrigger=false;
_rigidbody.velocity=velocity;
StartCoroutine(Explode());
}
IEnumerator Explode(){
yield return new WaitForSeconds(grenadeData.explodeDelay);
ParticleSystem effect=Instantiate(grenadeData.explosionEffect,transform.position,Quaternion.identity);
switch(grenadeData.grenadeType){
case GrenadeType.Frag://杀伤弹,爆炸后消失,特效存留一段时间
ExplodeDamage();
break;
}
if(grenadeData.explodeAudio){
MyAudioManager.Instance.MyPlaySound(effect.AddComponent<AudioSource>(),grenadeData.explodeAudio);
}
Destroy(effect.gameObject,effect.main.duration+effect.main.startLifetime.constant);
Destroy(gameObject,grenadeData.lifeTimeAfterExplode);
}
伤害判定
进行球形范围检测,检测到人物后再拿人物躯干和手榴弹做一次连线检测Physics.LineCast(),检测手榴弹和人物chest之间没障碍,再触发伤害。这里不再用人物的中枪触发器,只要人物碰撞体在爆炸范围内就受到一样的伤害。
void ExplodeDamage(){
Collider[] colliders=Physics.OverlapSphere(transform.position,
grenadeData.explodeRadius,1<<MyGameManager.characterLayer);
CharacterBase character;
for(int i=0;i<colliders.Length;i++){
if(colliders[i].TryGetComponent(out character)){
if(!Physics.Linecast(transform.position,character.
chest.position,1<<MyGameManager.groundLayer)){
character.TakeDamage(grenadeData.damage,"注意敌人");
}
}
}
}
声音
爆炸声音会在手榴弹爆炸并消失后持续一段时间,不能把声源放在手榴弹上并在爆炸时销毁或失活手榴弹,可以把声源放在粒子效果上。
人物动画
- 手榴弹动画放在上半身层,有一个空闲动画和投掷动画;
温雷功能
需求:按下鼠标拔环,拔环结束后如果没有松开则保持,开始计时。如果已经松开则抛出。
动画剪裁为拉环、抛出两段。
拉环和抛出各用一个trigger,按下鼠标设置拉环trigger,松开设置抛出trigger。这样即使立即按下抬起,抛出trigger也会保持着,直到拔环动作结束,直接出发抛出。

问题是在输入回调脚本和人物脚本分开的架构下,原本射击逻辑只关心鼠标是true还是false,现在扔雷要知道按下抬起的瞬间。要怎么修改?
禁止投掷中点击再触发投掷
这样投掷时连点两次,就会第二次触发pullRing,雷投出后就会直接再拔环,哪怕已经没有雷。那么我们还是加一个bool canThrow,按下鼠标判断canThrow才扔雷,然后把canThrow设false,拿出下一颗手榴弹后canThrow=true。
这个功能非常重要,没有做好会导致扔雷后没有手榴弹也做投掷动作,对于有温雷功能的还会出现拔环trigger比投出trigger多一个,卡在预备投出姿势,要再次松开鼠标才继续,然后回到预备姿势又立刻拔环的严重bug。
尝试用关键帧禁止投掷中点击再触发投掷
发现因为动画有过渡,点击完关键帧并不能立刻把canThrow设false,必须保证点击触发投掷后立刻禁止响应点击,直到下一个手榴弹准备好再响应点击。所以这里不适合用关键帧控制canThrow。
所以还是按下鼠标后立即canThrow=false,在投掷动画末尾通过动画事件找到下一个手榴弹,找到则canThrow=true。

拔环播放完再松开鼠标延迟一下才播放抛出?
手榴弹抛出需要调用多少业务逻辑?
- 手榴弹执行抛出方法,包括给手榴弹物体一个速度、启用碰撞体、开始爆炸计时;
- 背包里手榴弹数量-1;
- 通知HUD,手榴弹数量-1;
有时间差的三件事:拔环、投出、准备下一个,用动画事件调用好不好?
准备下一个手榴弹应该准备和当前的同一个类型的,所以前两件事的手榴弹信息需要留给投掷结束后的准备下一个手榴弹方法使用。

3624

被折叠的 条评论
为什么被折叠?



