• 用Unity模仿CSGO里的火焰效果


      CSGO里的火焰效果和真实的情况比较像,能沿着遮挡物前进,如下是模仿效果。

      

      思路比较简单,开始想的是一圈一圈发出去,但是前圈与后圈的联系不好做,换种思路,每个方向发射一条线,这样根据上一个位置的方位先向前进,如果前面有遮挡,则计算好新的位置与方向,反之前面没有遮挡,选择合适的位置,并从这个方向的上面向下检测,检测这个方向的垂直位置有没遮挡,如果有遮挡,计算新的方向与位置,没有,则表明延展不下去。如下图所示.

      

      相关主要代码:  

        //根据一条线的上一个节点,确定这个节点如何定位
        bool forward(ref Vector3 pos, ref Quaternion quat)
        {
            var forward = quat * Vector3.forward;
            var up = quat * Vector3.up;
    
            var nextPos = pos + forward * radius;
            RaycastHit hit;
            //前面有没挡住
            //Debug.DrawRay(nextPos, forward, Color.red, 100);
            var hitForward = Physics.Raycast(nextPos, forward, out hit, radius * 2);
            var hitDown = false;
            //如果前面没有挡住,检查垂直位置
            if (!hitForward)
            {
                //height用来控制垂直扩展的高度
                var upPos = pos + 2 * forward * radius + up * height * 2;
                //Debug.DrawRay(upPos, -up, Color.green, 100);
                hitDown = Physics.Raycast(upPos, -up, out hit, height * 4);
            }
            if (hitForward || hitDown)
            {
                //新的位置
                pos = hit.point + hit.normal * prefabHeight;
                //Debug.DrawRay(pos, hit.normal, Color.blue, 100);
                quat = Quaternion.FromToRotation(up, hit.normal) * quat;
                return true;
            }
            return false;
        }
    forward

      如上代码每句都加上了注释,我们添加了一个radius参数,用来表示每次前进的步子大小,radius越小,则越密集,然后添加一个height参数用来表示垂直方向升降能力,可以看下图。

      确定一条线上显示后,我们只要考虑沿着周围全部扩展就行,在这我们用level表示扩展多少层,层数越多,我们每条线的相隔的角度也应该越小,如下效果。

      

      相关代码:  

        //分成多条线向外扩散
        public void extend()
        {
            //分成多条线
            count = (int)(2 * level * Mathf.PI);
            //每条偏移角度
            angle = 360.0f / count;
            var up = transform.rotation * Vector3.up;
            for (int i = 0; i < count; i++)
            {
                var curAngle = angle * i;
                var curQuat = Quaternion.AngleAxis(curAngle, up);
                var quat = curQuat * transform.rotation;
                //forward(transform.position, quat, level, i);
                StartCoroutine(forward(transform.position, quat, level, i));
            }
        }
    
        //每条线向前扩展,自动翻越遮挡
        public IEnumerator forward(Vector3 pos, Quaternion quat, int level, int place)
        {
            var npos = pos;
            var nquat = quat;
            int curLevel = 1;
    
            var prePos = npos;
            while (curLevel <= level && forward(ref npos, ref nquat))
            {
                //Debug.DrawLine(prePos, npos, Color.blue, 100f, true);
                if (bCreate(curLevel, place))
                {
                    var offset = offsetPos(nquat, 1);
                    var obj = Instantiate(getPrefab(curLevel), npos + offset, nquat);
                    //Debug.DrawLine(prePos, npos, Color.blue, 100f, true);
                    var duration = getDuration(curLevel);
                    Destroy(obj, duration);
                }
                yield return new WaitForSeconds(deltaTime);
                curLevel++;
                prePos = npos;
            }
        }
    extend

      这些蓝线确定了火焰沿线步局,但是现在最中心会比较密集,我们需要有选择的是否生成,如下图:

      

      相关代码:  

        //确定当前层上的某个位置是否需要生成
        bool bCreate(int level, int place)
        {
            //当前需要显示的个数
            var levelCount = (int)(2 * level * Mathf.PI);
            var levelLength = Mathf.RoundToInt((float)count / levelCount);
            //每层起点偏移一点位置
            var offset = place + level;
            return offset % levelLength == 0;
        }
    bCreate

      这样就能简单在每层生成合适的个数,其中最上面有个prefabHeight,用来控制模型中心与下边的高度,用来控制生成的模型贴着遮挡物,如图:

       

      如下是完整代码:  

    using UnityEngine;
    using System.Collections;
    
    public class FireMove : MonoBehaviour
    {
        public GameObject[] particlePrefabs;
        [Tooltip("半径越小,模型越密集")]
        public float radius = 0.5f;
        [Tooltip("高度越大,模型能越过更高的遮挡物")]
        public float height = 0.5f;
        [Tooltip("层级越多,向外扩展的导数越多")]
        public int level = 10;
        [Tooltip("每层间隔生成时间")]
        public float deltaTime = 0.1f;
        [Tooltip("控制火焰显示时间")]
        public float duration = 5.0f;
        [Tooltip("水平偏移")]
        public float offsetHeight = 0.1f;
        [Tooltip("高度偏移")]
        public float offsetPanel = 0.2f;
        [Tooltip("模型与地面的距离")]
        public float prefabHeight = 0.01f;
    
        private int count;
        private float angle;
    
    #if UNITY_EDITOR       
        public void Update()
        {
            if (Input.GetKeyDown(KeyCode.K))
            {
                extend();
            }
        }
    #endif
    
        public void attack(int level)
        {
            this.level = level;
            extend();
        }
    
        //分成多条线向外扩散
        public void extend()
        {
            //分成多条线
            count = (int)(2 * level * Mathf.PI);
            //每条偏移角度
            angle = 360.0f / count;
            var up = transform.rotation * Vector3.up;
            for (int i = 0; i < count; i++)
            {
                var curAngle = angle * i;
                var curQuat = Quaternion.AngleAxis(curAngle, up);
                var quat = curQuat * transform.rotation;
                //forward(transform.position, quat, level, i);
                StartCoroutine(forward(transform.position, quat, level, i));
            }
        }
    
        //每条线向前扩展,自动翻越遮挡
        public IEnumerator forward(Vector3 pos, Quaternion quat, int level, int place)
        {
            var npos = pos;
            var nquat = quat;
            int curLevel = 1;
    
            var prePos = npos;
            while (curLevel <= level && forward(ref npos, ref nquat))
            {
                //Debug.DrawLine(prePos, npos, Color.blue, 100f, true);
                if (bCreate(curLevel, place))
                {
                    var offset = offsetPos(nquat, 1);
                    var obj = Instantiate(getPrefab(curLevel), npos + offset, nquat);
                    //Debug.DrawLine(prePos, npos, Color.blue, 100f, true);
                    var duration = getDuration(curLevel);
                    Destroy(obj, duration);
                }
                yield return new WaitForSeconds(deltaTime);
                curLevel++;
                prePos = npos;
            }
        }
    
        //确定当前层上的某个位置是否需要生成
        bool bCreate(int level, int place)
        {
            //当前需要显示的个数
            var levelCount = (int)(2 * level * Mathf.PI);
            var levelLength = Mathf.RoundToInt((float)count / levelCount);
            //每层起点偏移一点位置
            var offset = place + level;
            return offset % levelLength == 0;
        }
        public GameObject getPrefab(int curLevel)
        {
            var prfabCount = particlePrefabs.Length;
            //var t = Mathf.RoundToInt(prfabCount * (curLevel - 1) / level);
            var index = Random.Range(0, prfabCount);
            return particlePrefabs[index];
        }
    
        //加上偏移
        public Vector3 offsetPos(Quaternion quat, int level)
        {
            var xAxis = quat * Vector3.right * Random.Range(-offsetPanel, offsetPanel) * level;
            var zAxis = quat * Vector3.forward * Random.Range(-offsetPanel, offsetPanel) * level;
            var yAxis = quat * Vector3.up * Random.Range(-offsetHeight, offsetHeight) * level;
            return xAxis + zAxis + yAxis;
        }
    
        //每层的生命周期
        float getDuration(int level)
        {
            var levelDuration = duration - 2 * level * deltaTime;
            return levelDuration;
        }
    
        //根据一条线的上一个节点,确定这个节点如何定位
        bool forward(ref Vector3 pos, ref Quaternion quat)
        {
            var forward = quat * Vector3.forward;
            var up = quat * Vector3.up;
    
            var nextPos = pos + forward * radius;
            RaycastHit hit;
            //前面有没挡住
            //Debug.DrawRay(nextPos, forward, Color.red, 100);
            var hitForward = Physics.Raycast(nextPos, forward, out hit, radius * 2);
            var hitDown = false;
            //如果前面没有挡住,检查垂直位置
            if (!hitForward)
            {
                //height用来控制垂直扩展的高度
                var upPos = pos + 2 * forward * radius + up * height * 2;
                //Debug.DrawRay(upPos, -up, Color.green, 100);
                hitDown = Physics.Raycast(upPos, -up, out hit, height * 4);
            }
            if (hitForward || hitDown)
            {
                //新的位置
                pos = hit.point + hit.normal * prefabHeight;
                //Debug.DrawRay(pos, hit.normal, Color.blue, 100);
                quat = Quaternion.FromToRotation(up, hit.normal) * quat;
                return true;
            }
            return false;
        }
    }
    FireMove

      直接放到一个物体上就可以了,其中radius设为相应的火焰半径大小就可,如果要密一些,降低一些就行,相应offset参数用来设定水平与垂直上的偏移,免的给人太规律了,代码上都有详细注释,可以自己改动。

  • 相关阅读:
    JMM、asifserial语义、happensbefore模型
    jvm内存溢出实践
    垃圾回收和GC算法
    jvm运行时数据区域
    实现加锁的消费者和生产者模型
    jvm监控工具小结
    HotSpot对象
    java常用垃圾收集器
    字面量, 符号引用与直接引用
    常用线程安全的队列
  • 原文地址:https://www.cnblogs.com/zhouxin/p/5910257.html
Copyright © 2020-2023  润新知