文章目录


Hello大家好我是开罗小8,今天我来给大家带来Unity常用子弹弹幕类型的实现,功能的实现方式有很多种,在实现效果中,我会尽可能用简单的代码实现,文章的示例项目文件在文章底部

环形弹幕实现
追踪弹

前言

本文子弹始终朝向其前进方向移动(在2D中红轴为前进方向,3D中蓝轴为前进方向),可以将坐标轴切换为local来查看当前物体的朝向

 子弹的基类脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BaseBullet : MonoBehaviour
{
    public float Speed=1;
    public GameObject ExpFX;
    protected Rigidbody2D rb;
    protected Transform m_tansform;
    // Update is called once per frame
    private void Awake()
    {
        rb = gameObject.AddComponent<Rigidbody2D>();
        rb.gravityScale = 0;
        rb.drag= 0;
        rb.freezeRotation = true;
        rb.bodyType = RigidbodyType2D.Dynamic;
        rb.constraints = RigidbodyConstraints2D.FreezeRotation;
        m_tansform = transform;
    }
    protected virtual void Update()
    {
        m_tansform.position= m_tansform.position+ m_tansform.right*Speed*Time.deltaTime;
        //m_tansform.Translate(Vector3.right * Speed * Time.deltaTime);
    }
    public void Explode()
    {
        Destroy(Instantiate(ExpFX, m_tansform.position,Quaternion.identity),2f);
        Destroy(gameObject);
    }
}
  • 脚本中Update两行代码均可实现让子弹沿自身的红轴(X轴)移动
  • 获取物体当前的轴向
  1. transform.right          红轴(X轴)
  2. transform.up             绿轴(Y轴)
  3. transform.forward     蓝轴(Z轴)
  • transform.right与Vector3.right的区别
  1. Vector3.right始终等于new Vector3(1,0,0)
  2. transform.right为当前物体红轴的朝向,受到物体旋转的影响 

transform.Translate方法如果只传入一个Vector3类型的,它会以自身的轴来进行移动

例如transform.Translate(new Vector3(1,0,0))表示物体朝自身的X轴方向移动1米

为了避免子弹相互碰撞,需要将子弹设置一个新的layer,命名为Bullet,layer设置完后还需要在Edit->ProjectSetting->Physics2D中取消勾选Bullet与Bullet之间的碰撞

一、各种类型子弹以及武器的实现

1.向鼠标方向发射子弹

using UnityEngine;

public class Weapon : MonoBehaviour
{
    public GameObject FireFX;
    public GameObject Bullet;
    private Transform m_transform;
    
    // Start is called before the first frame update
    Camera cam;
    void Start()
    {
        cam=Camera.main;
        m_transform = transform;
    }

    // Update is called once per frame
    void Update()
    {
        
        Vector3 mousePos= cam.ScreenToWorldPoint(Input.mousePosition);
        Vector3 weaponDir=mousePos- m_transform.position;
        weaponDir.z = 0;
        weaponDir.Normalize();
        m_transform.right=weaponDir;
        //float zAngle = Mathf.Atan2(weaponDir.y, weaponDir.x) * Mathf.Rad2Deg;
        //m_transform.localEulerAngles=new Vector3(0,0,zAngle);
        if(Input.GetKeyDown(KeyCode.Space))
        {
            //GameObject fireFX = Instantiate(FireFX);
            //fireFX.transform.position= m_transform.position;
            //Destroy( fireFX ,2f);
            GameObject bullet= Instantiate(Bullet);
            bullet.transform.position= m_transform.position;
            bullet.transform.right= m_transform.right;
        }
    }
}

1.使用本地变量缓存主摄像机和transform,使用transform获取变换和使用Camera.main获取摄像机比较消耗性能

2.将鼠标位置从屏幕空间转换到世界空间,使用cam.ScreenToWorldPoint(Input.mousePosition);传入当前鼠标的屏幕位置

注:屏幕位置为左下角为(0,0),右上角为屏幕分辨率,如(1920,1080)

3.计算向量,两个物体的位置相减,得到的方向指向被减的位置,示例中的代码可以得到武器位置指向鼠标的方向。

4.计算武器的旋转角,调整武器的朝向,同时在生成子弹时调整子弹的位置和旋转。

注:直接对transform.right赋值改变物体旋转只适合在2D中使用,不推荐在3D情况下使用,可能会导致其他轴的不正确旋转

2.子弹撞墙反弹

using UnityEngine;

public class BounceBullet : BaseBullet
{
    public int BounceCount = 3;


    private void OnCollisionEnter2D(Collision2D collision)
    {

        
        if (collision.gameObject.CompareTag("Wall"))
        {
            --BounceCount;
            if (BounceCount <= 0)
            {
                Explode();
                return;
            }
            Vector2 outDir = Vector2.Reflect(m_tansform.right, collision.GetContact(0).normal);
            m_tansform.right = outDir;
        }
    }
    
}

  • Vector2.Reflect第一个参数传入当前子弹的朝向,第二个参数传入碰撞的法向,会返回反射后的新向量
  • collision.GetContact(0).normal来获取碰撞点的法向 
  • 只有在OnCollisionEnter2D中才可以获取碰撞点的法向,OnTriggerEnter2D中无法获取碰撞点的法向
  • 子弹需要有Rigidbody2D组件,且刚体类型为Dynamic,否则碰撞函数不会被触发
  • 此处使用CompareTag方法比较碰撞体的标签,如果是Wall就反弹,使用CompareTag比直接gameObject.tag==“Wall”要更节省性能
  • 应当保证传入的参数都是归一化(向量的模为1)后的向量,可以使用vector3.Normalize();方法使向量归一化,否则计算出的方向可能会不正确
  • 可以直接给transform.right赋值来改变物体的朝向

3.子弹追踪敌人

简单版,这样写子弹会固定指向敌人,视觉效果并不好

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TargetingBullet : BaseBullet
{
    Transform target;
    private void Start()
    {
        target = GameObject.Find("Target").transform;
    }
    // Update is called once per frame
    protected override void Update()
    {
        Vector3 targetDir=target.position-transform.position;
        targetDir.Normalize();

        transform.right= targetDir;
        base.Update();
    }

}

 调整方法1:使用插值函数,子弹每次调整一定的角度

using UnityEngine;

public class TargetingBullet : BaseBullet
{
    Transform target;
    public float TurnSpeed=2;
    float lifeTime=0;
    private void Start()
    {
        target = GameObject.Find("Target").transform;
    }
    // Update is called once per frame
    protected override void Update()
    {
        Vector3 targetDir=target.position-transform.position;
        targetDir.z = 0;
        targetDir.Normalize();
        lifeTime += Time.deltaTime;
        
        m_tansform.right= Vector3.Slerp(m_tansform.right,targetDir,Mathf.Clamp01(Time.deltaTime*(lifeTime)* TurnSpeed));
        base.Update();
    }
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            Explode();
        }
    }
}

lifeTime的作用:子弹存在的时间越长,转向速度越快,如果不增加转向速度,会导致子弹速度大于转向速度时,子弹会一直绕着敌人转圈,无法命中敌人 

Vector3.Slerp:前两个参数传入两个Vector3类型,第三个参数传入插值,当插值为0时返回第一个向量,插值为1时返回第二个向量,插值在0~1时返回两向量的过渡值

4.散弹枪

public void FireShotGun(float angle,int bulletCount)
    {
        int mid = bulletCount >> 1;
        for(int i=0;i<bulletCount; i++)
        {
            Vector3 dir =  Quaternion.Euler(new Vector3(0,0, (i - mid) * angle))*m_transform.right;
            GameObject bullet = Instantiate(Bullet);
            bullet.transform.position = m_transform.position;
            bullet.transform.right = dir;
        }
    }

angle为每个子弹的角度,bulletCount为子弹的个数,示例中传入的参数为10,4

1.使用位运算计算子弹数量的中位数,buttonCount>>1等价于buttonCount/2,位运算的计算效率比除法要高,但是位运算的优先级低于加减法,如果有加法要注意加括号

2. Quaternion.Euler传入的Vector3类型相当于旋转的的欧拉角度,乘以一个向量的返回值等于该向量在对应轴上旋转后的向量,注意四元数在乘号左边,向量在右边,位置不能交换,上面的代码在计算transform.right绕自身的z轴旋转一个角度后的向量

5.圆环弹幕

public void FireCircle(int bulletCount,float angleOffset)
    {
        float angle=360/bulletCount;
        for (int i = 0; i < bulletCount; i++)
        {
            Vector3 dir = Quaternion.Euler(new Vector3(0, 0, i*angle+angleOffset)) * m_transform.right;
            GameObject bullet = Instantiate(Bullet);
            bullet.transform.position = m_transform.position;
            bullet.transform.right = dir;
        }
    }

 每次发射增加angleoffset即可产生不同的角度偏移

其他类型的弹幕会持续更新…… 

项目文件下载:

子弹和武器.unitypackage


总结

本文介绍了常用的2D游戏子弹以及武器的实现方式,为了方便,直接对transform.right赋值来调整旋转,这个操作只能保证transform.right这个轴朝向给定的方向,在3D中可能会导致奇怪的旋转,我会在下一篇文章中详细介绍使用数学以及四元数的方法计算旋转

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐