johnsoncodehk 发表于 2022-9-13 09:26

我们来用Unity3D做一下弹幕

前言:

《东方》系列,以其华丽丽的弹幕以及高手行云流水的操作给到了咱们深刻的印象。
那么在Unity中,我们如何来实现这些内容呢?请看下文。
基础篇:

    1.散弹弹幕
    大部分STG游戏中都有散弹这一种弹幕样式。其实质,就是在发射方向的左右两边,偏移相同的角度,在同一帧内发射方向不同的多颗子弹,造成散射的感觉(此处不讨论无规则的散弹模式)。原理如下图:



散弹弹幕原理图示

    只要能够让发射方向能够按照我们的想法进行旋转,就能够实现想要的效果。此处使用协程来完成。实现代码如下:
    IEnumerator FirShotgun()
    {
      Vector3 bulletDir = firPoint.transform.up; //由于资源的原因,我们这边的发射方向为物体的Up轴方向
      Quaternion leftRota = Quaternion.AngleAxis(-30, Vector3.forward);
      Quaternion RightRota = Quaternion.AngleAxis(30, Vector3.forward); //使用四元数制造2个旋转,分别是绕Z轴朝左右旋转30度
      for (int i=0;i<10;i++)   //散弹发射次数
      {
            for (int j=0;j<3;j++) //一次发射3颗子弹
            {
                switch (j)
                {
                  case 0:
                        CreatBullet(bulletDir, firPoint.transform.position);//发射第一颗子弹,方向不需要进行旋转。参数为子弹运动方向与生成位置,函数实现未列出。
                        break;
                  case 1:
                        bulletDir = RightRota * bulletDir;//第一个方向子弹发射完毕,旋转方向到下一个发射方向
                        CreatBullet(bulletDir, firPoint.transform.position);//调用生成子弹函数,参数为发射方向与生成位置。
                        break;
                  case 2:
                        bulletDir = leftRota*(leftRota * bulletDir); //右边方向发射完毕,得向左边旋转2次相同的角度才能到达下一个发射方向
                        CreatBullet(bulletDir, firPoint.transform.position);
                        bulletDir = RightRota * bulletDir; //一轮发射完毕,重新向右边旋转回去,方便下一波使用
                        break;
                }
            }
            yield return new WaitForSeconds(0.5f); //协程延时0.5秒进行下一波发射
      }
    }
    完成后效果如图:



散弹弹幕效果图示

2.圆形弹幕
    圆圈形状的弹幕在各种游戏中出现的频次也是最高的。很多华丽的弹幕也是由基础圆形弹幕组成的。其原理如下图:



圆形弹幕原理图示

    实现方法类似散弹弹幕,只不过要将发射的子弹刚好组成一个圆圈,就需要每个子弹发射方向之间的角度相等,且相加刚好为360度才行。其实现原理如下:
    IEnumerator FirRound(int number,Vector3 creatPoint)//参数为发射波数与子弹生成点
    {
      Vector3 bulletDir = firPoint.transform.up;//发射方向
      Quaternion rotateQuate = Quaternion.AngleAxis(10, Vector3.forward);//使用四元数制造绕Z轴旋转10度的旋转
      for (int i=0;i< number; i++)    //发射波数
      {
            for (int j=0;j<36;j++)
            {
                CreatBullet(bulletDir, creatPoint);   //生成子弹
                bulletDir = rotateQuate * bulletDir; //让发射方向旋转10度,到达下一个发射方向
            }
            yield return new WaitForSeconds(0.5f); //协程延时,0.5秒进行下一波发射
      }
      yield return null;
    }    完成后效果如图:



圆形弹幕效果图示

进阶篇:

接下来是一些更接近于市面上商业游戏的复杂例子。一起来看看:
1.密集型弹幕



游戏截图——东方地灵殿中的密集型弹幕

    弹幕分析:
    上图中,首先是一个8方向的圆形弹幕,当子弹到达目标点后,再在各自当前的点生成N波多方向的圆形弹幕,塞满大半个屏幕。其实现原理如下图:



密集型弹幕原理图示

    通过上面的分析我们能很快得出,其实该弹幕就是不同角度以及不同位置圆形弹幕的组合使用。实现代码如下:
    IEnumerator FirRoundGroup()
    {
      Vector3 bulletDir = firPoint.transform.up;
      Quaternion rotateQuate = Quaternion.AngleAxis(45, Vector3.forward);//使用四元数制造绕Z轴旋转45度的旋转
      List<BulletCharacter> bullets = new List<BulletCharacter>();       //装入开始生成的8个弹幕
      for (int i=0;i<8;i++)
      {
            var tempBullet = CreatBullet(bulletDir, firPoint.transform.position);
            bulletDir = rotateQuate * bulletDir; //生成新的子弹后,让发射方向旋转45度,到达下一个发射方向
            bullets.Add(tempBullet);
      }
      yield return new WaitForSeconds(1.0f);   //1秒后在生成多波弹幕
      for (int i = 0; i < bullets.Count; i++)
      {
            bullets.speed = 0; //弹幕停止移动
            StartCoroutine(FirRound(6, bullets.transform.position));//通过之前弹幕的位置,生成多波多方向的圆形弹幕。这里调用了上面写过的圆形弹幕函数
      }
    }    完成后效果如图:



密集型弹幕效果图示

    此处弹幕并没有完全复刻,最后发射完成后的有间隔的子弹,自动组合成一条直线的小细节,已经超出本文范畴,有兴趣的同学可以自己尝试分析一下原理。
2.涡轮型弹幕



游戏截图——东方地灵殿中的涡轮型弹幕

    涡轮型弹幕其实质就是一个生成半径不断增长的圆形弹幕,然后混合了上面密集型弹幕的一个特征,在生成的位置再次生成一个多方向的圆形弹幕。其实现原理如下:



涡轮型弹幕原理图示

    这个弹幕主要难点就是找到下一个生成弹幕点(其实也不算难,上面都已经实现过了)。实现代码如下:
    IEnumerator FireTurbine()
    {
      Vector3 bulletDir = firPoint.transform.up;      //发射方向
      Quaternion rotateQuate = Quaternion.AngleAxis(20, Vector3.forward);//使用四元数制造绕Z轴旋转20度的旋转
      float radius = 0.6f;      //生成半径
      float distance = 0.2f;      //每生成一次增加的距离
      for (int i=0;i<18;i++)
      {
            Vector3 firePoint = firPoint.transform.position + bulletDir * radius;   //使用向量计算生成位置
            StartCoroutine(FirRound(1, firePoint));   //在算好的位置生成一波圆形弹幕
            yield return new WaitForSeconds(0.05f);   //延时较小的时间(为了表现效果),计算下一步
            bulletDir = rotateQuate * bulletDir;      //发射方向改变
            radius += distance;   //生成半径增加
      }
    }    完成后效果如图:



涡轮型弹幕效果图示

拓展篇:

    球形弹幕



球型弹幕效果图示

    上面的球形弹幕,仅供拓展使用。有兴趣的童鞋,可以自己观察效果图,分析出原理自己实现。这里就不贴出原理以及代码图了,其实现代码我将一并打包进入工程,可以参考一下。
优化篇:

    此篇文章旨在教会大家制作简单弹幕,并没有做任何优化的操作。而在实际游戏当中,同屏出现大量子弹会占用大量的内存资源,是必须做优化的。我在这提出几点优化建议,有兴趣的同学可以研究一二。
    1.使用对象池优化,将游戏物体重复利用。关于对象池的的原理就不再细说,详情查阅以下链接:【Unity】工具类系列教程——对象池!
    2.自定义mesh。将场景中的子弹,全部使用通过代码进行绘制,不使用游戏物体的模式来进行交互。同时子弹与周围物体的碰撞检测,也是自己代码实现,这样效果将会更好。
    3.当场景的中的子弹都是存在单独逻辑,且想要进行多核优化的,可以使用Unity的ECS模式进行优化。详情查看以下链接:Unity 实体组件系统(ECS)——预览与体验
结语:

    本篇文章大部分都是在进行数学运算,对童鞋的数学基础有一定的要求。不太明白的同学,是时候补一下数学了。
    工程地址: https://pan.baidu.com/s/1IiR0Zs_VR0AIw3r1Ese6Pg
    提取码: raa5
    友情提示:参考工程使用的Unity3D版本是2018.3.0b7,如下载打开不能运行或者报错,记得切换一下目标版本,重新尝试。
其他内容:

    想到线下学习游戏开发的童靴,猛戳这里:http://levelpp.com/
    另有专业开发交(gao)流(ji)群等待大家强势插入:869551769

johnsoncodehk 发表于 2022-9-13 09:27

学到了!!!

ainatipen 发表于 2022-9-13 09:36

看到弹幕首先想的是伤害结算,然后是子弹身上自己的脚本,然后是子弹作为抛射物放进缓存池里优化,然后是所有抛射物的 Update 函数用一个管理器来调度……

TheLudGamer 发表于 2022-9-13 09:39

你这个是做游戏逻辑的想法,本篇文章是只做游戏形状想法,我们2个结合才是一个弹幕游戏。骚年,现在上还来的及!
[机智]

ChuanXin 发表于 2022-9-13 09:48

东方的世界好可怕,这么密的弹幕不是人玩的惹

rustum 发表于 2022-9-13 09:56

居然不是我想的那种弹幕

XGundam05 发表于 2022-9-13 09:59

前两天工作中刚做到这个,用UniRX解决的

BlaXuan 发表于 2022-9-13 10:05

写的好棒

FeastSC 发表于 2022-9-13 10:08

抓到大林一只,哈哈上了我的推荐

RhinoFreak 发表于 2022-9-13 10:09

学习到了
页: [1] 2
查看完整版本: 我们来用Unity3D做一下弹幕