Unity常用2D子弹弹幕以及枪械类型的实现:可实现霰弹枪,Boss弹幕,子弹反弹,追踪弹等
本文介绍了常用武器和子弹的简易实现方式
文章目录
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轴)移动
- 获取物体当前的轴向
- transform.right 红轴(X轴)
- transform.up 绿轴(Y轴)
- transform.forward 蓝轴(Z轴)
- transform.right与Vector3.right的区别
- Vector3.right始终等于new Vector3(1,0,0)
- 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即可产生不同的角度偏移
其他类型的弹幕会持续更新……
项目文件下载:
总结
本文介绍了常用的2D游戏子弹以及武器的实现方式,为了方便,直接对transform.right赋值来调整旋转,这个操作只能保证transform.right这个轴朝向给定的方向,在3D中可能会导致奇怪的旋转,我会在下一篇文章中详细介绍使用数学以及四元数的方法计算旋转
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)