Unity射击游戏手榴弹笔记

功能

  1. 按3如果没在用手榴弹,则拿出手榴弹。如果在用,则切换下一种手榴弹。没有则空手;
  2. 只有投掷准备状态点击投掷。按下鼠标后直到拿出下一个手榴弹回到投掷准备状态时不响应点击;

数据

  1. 新增手榴弹配表数据类,新建手榴弹配表,配表管理器新增手榴弹配表字典,初始化增加读取配表并转换成字典的代码,增加根据id得到手榴弹详情的方法;
  2. 手榴弹的预制体需要可拾取的、扔出的;
  3. 背包界面增加背包内的手榴弹、场景里的手榴弹、别人背包的手榴弹格子预制体和类;
  4. 人物有一个int grenadeIndex记录当前拿着的手榴弹在背包手榴弹列表的索引,没拿手榴弹时是-1;
  5. 每种手榴弹有一个待拾取预制体和一个生效预制体。待拾取预制体有一个手榴弹模型、一个触发器用来被玩家检测到,一个数据脚本记录自己是哪种手榴弹,没有投掷、爆炸相关功能。生效预制体有刚体碰撞体管理运动,投掷、爆炸相关功能。

用枚举记录手榴弹类型

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;
    }

逻辑

拾取和使用

  1. 在交互系统的拾取物品分支增加对手榴弹类的判断;
  2. 按拿出手榴弹的按键时
    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;
                }
            }
        }

  3. GetNextGrenade():在背包的手榴弹列表遍历,有和grenadeIndex类型不同的手榴弹则返回索引,没有则返回-1;
  4. UseGrenade(),先执行grenadeIndex=GetNextGrenade(),在右手实例化手榴弹,没有则回到空手;
  1. 扔手榴弹方法通过动画事件执行
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,"注意敌人");
                        }
                    }
                }
            }

    声音

    爆炸声音会在手榴弹爆炸并消失后持续一段时间,不能把声源放在手榴弹上并在爆炸时销毁或失活手榴弹,可以把声源放在粒子效果上。

    人物动画

    1. 手榴弹动画放在上半身层,有一个空闲动画和投掷动画;

    温雷功能

    需求:按下鼠标拔环,拔环结束后如果没有松开则保持,开始计时。如果已经松开则抛出。

    动画剪裁为拉环、抛出两段。

    拉环和抛出各用一个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. 手榴弹执行抛出方法,包括给手榴弹物体一个速度、启用碰撞体、开始爆炸计时;
    2. 背包里手榴弹数量-1;
    3. 通知HUD,手榴弹数量-1;

    有时间差的三件事:拔环、投出、准备下一个,用动画事件调用好不好?

    准备下一个手榴弹应该准备和当前的同一个类型的,所以前两件事的手榴弹信息需要留给投掷结束后的准备下一个手榴弹方法使用。

    内容概要:本文详细记录了对一个Android ARM64静态ELF文件中字符串加密机制的逆向分析过程。该ELF文件的所有字符串均被加密,无法通过常规strings命令或IDA直接识别。作者通过分析发现,加密字符串存储在.rodata段,其解密所需信息(包括密文地址、长度和16位密钥)保存在.data.rel.ro段的40字节描述符中。核心解密函数sub_10F408采用自反的双pass流密码算法,结合固定密钥KEY_TERM(由.data段24字节数据计算得出),实现字节级非线性、位置与长度相关的加密。文章还复现了完整的Python解密脚本,并揭示了该保护机制的本质为代码混淆而非强加密,最终成功批量解密全部956条字符串,暴露程序真实行为,如shell命令模板、设备标识篡改、网络重置等操作。此外,文中还提及未启用的自定义壳框架及其反dump设计。; 适合人群:具备逆向工程基础的安全研究人员、二进制分析人员及对ELF保护技术感兴趣的开发者。; 使用场景及目标:①学习ELF二进制中字符串加密的典型实现方式与逆向突破口;②掌握从结构识别、函数追踪到算法还原的完整逆向流程;③理解“绑定二进制”的完整性校验设计及其局限性;④实践编写IDAPython脚本自动化提取与解密敏感数据。; 阅读建议:此资源以实战案例驱动,不仅展示技术细节,更强调逆向思维与验证方法,建议读者结合IDA调试环境,逐步跟随文中步骤进行动态分析与算法验证,深入理解每一步的推理依据。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值