先放效果和实现目标:

  • 激光武器有攻击范围,超出范围的对象不会与激光发生碰撞;
  • 在攻击范围内的对象会与激光发生碰撞并且阻挡激光。同时,考虑到特殊情况,能与激光发生碰撞的对象应该是可编程的;
  • 在操作上,激光向鼠标点击方向发射,鼠标松开后不再发射。同时,为了让激光发射的过程看起来更加的自然,应该在激光消失前添加一个逐渐缩小的效果;

一、 创建一个激光对象

  1. 为了提高复用性,构造Prefab对象,在使用时直接生成到世界坐标系中。所以先构造一个激光的Prefab;

  2. 选取一个合适的Renderer。有两种Renderer比较适合做2D的激光效果,它们是LineRenderer和SpriteRenderer。LineRenderer的好处是不用自己画素材,激光拉伸时不会出现畸变,坏处是丑,要好看的话得自己写Shader;而SpriteRenderer的好处是好不好看,美工来干,坏处是激光拉伸会畸变。我们简单的了解一下这两种Renderer;

  • LineRenderer的构造如下图。先创建Line对象,再修改右侧Inspector面板红框里的参数,让Line垂直于X轴,之后在inspector面板里改颜色,改材质。可以看到,用现有的材质效果并不太好;


  • SpriteRenderer的构造如下图。创建完对象之后,直接修改Sprite就好了(inspector面板中的其他组件之后会讲)。在之后的步骤中,我们都直接使用SpriteRenderer的激光对象;

二、添加碰撞体与碰撞检测。

进行实际操作之前,我们还需要了解Unity当中的几个属性和方法:

  • Physics2D.Raycast()。这个方法通过射线投影的方式,直接获取射线方向上第一个含有碰撞体组件的对象的引用。这个方法本身不能通过tag这种灵活的方式来控制是否碰撞,所以难以实现穿透敌人却穿不透墙壁的功能;
  • Physics2D.RaycastAll() 。这个方法相当灵活,它可以获取射线方向,射线范围内的所有含有碰撞体组件的对象的引用。通过获取对象数组的方式,我们进行二次的排除,便可以判断什么情况下碰撞,什么情况下穿透。另外,这个方法可以添加一个distance参数,使得从起点到终点距离大于distance的物体不会被检测,这刚好符合我们激光武器具有攻击范围的要求;
  • RaycastHit2D中的distance属性。Physics2D.RaycastAll()返回的结果是基于RaycastHit2D的对象数组。如果我们需要获取第一个有效碰撞体,那么就需要对碰撞体的距离进行排序,取到与发射者距离最近的那个物体,distance属性表示的就是射线的起点到碰撞体的距离。
  1. 把Prefab的tag设置为Bullet(其他的tag什么也行,这是用来判断是否碰撞的);

  2. 在当前Prefab的右侧面板中,分别加入Rigidbody2D、BoxCollider2D,把BoxCollider2D的"Is trigger"勾选上并且修改的碰撞体大小(加这两个组件的主要原因是可以实现激光的对波效果,如果希望激光会互相穿透,不加也可以);

  3. 新建一个组件,命名为“Laser Weapon Listenser”。这个类主要用来让发射后的激光对象能够独立的控制自己的方向和形状,与发射者对象实现解耦。该类的总体思路是:先获取RaycastAll的所有碰撞对象,再通过距离排序法获取第一个碰撞物体,并将原长度的激光Prefab的y轴压缩至distance的长度,在玩家释放鼠标后,压缩激光Prefab的x轴,使之逐渐消失。碰撞类的内部的完整代码如下;

using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 激光监听器脚本。
/// </summary>
public class LaserWeaponListener : MonoBehaviour
{#region 成员变量[SerializeField] [Tooltip("激光发射者的标签。")] public string brickTag = null;[SerializeField] [Tooltip("激光攻击频率。")] public float attackRate = 0.1F;[SerializeField] [Tooltip("激光攻击范围。")] public float attackRange = 1F;[SerializeField] [Tooltip("激光prefab对象的实际长度。")] public float length = 1F;private Vector3 ray = Vector3.zero;private Vector3 scale = Vector3.zero;private bool isAttacking = false;private float fadeOutRatio = 0.02F; // 攻击结束后,激光宽度的缩减系数private float countTime = 0;        // 攻击频率计时器private LaserWeaponController laserWeaponController = null;#endregion#region 属性控制/// <summary>/// 射击方向。/// </summary>public Vector3 Ray{get{return ray;}set{ray = value;}}/// <summary>/// 是否处于攻击状态。/// </summary>public bool IsAttacking{get{return isAttacking;}set{isAttacking = value;}}/// <summary>/// 激光发射者控制器。/// </summary>public LaserWeaponController LaserWeaponController{get{return laserWeaponController;}set{laserWeaponController = value;}}#endregion#region 基础私有方法/// <summary>/// 在帧刷新时触发。/// </summary>private void Update(){transform.position = laserWeaponController.gameObject.transform.position;   // 把激光发射点定在发射者原点if (isAttacking)Attacking();elseDeAttacking();isAttacking = false;}/// <summary>/// 当触发器检测到物理接触时触发。/// </summary>/// <param name="collision">碰撞物体对象。</param>private void OnTriggerStay2D(Collider2D collision){if (!CanCollide(collision.gameObject))return;if (Time.time > countTime)countTime = Time.time + attackRate;// 以下为目标对象收到伤害的代码,略}/// <summary>/// 攻击。/// </summary>private void Attacking(){RaycastHit2D[] hits = Physics2D.RaycastAll(transform.position, ray, attackRange);List<RaycastHit2D> colliders = new List<RaycastHit2D>();for (int i = 0; i < hits.Length; i++)if (hits[i] && CanCollide(hits[i].transform.gameObject))colliders.Add(hits[i]);float distance = GetNearestHitDistance(colliders);scale = new Vector3(1, distance / length, 1);  // 压缩激光的y轴长度transform.localScale = scale;}/// <summary>/// 去攻击。/// </summary>private void DeAttacking(){scale = new Vector3(scale.x - fadeOutRatio, scale.y, 1);transform.localScale = scale;if (scale.x <= 0)gameObject.SetActive(false);}/// <summary>/// 得到所有与射线碰撞的对象中,碰撞模长最小的线段长度。/// </summary>/// <param name="hits">射线碰撞对象列表。</param>/// <returns>碰撞模长最小的线段长度。</returns>private float GetNearestHitDistance(List<RaycastHit2D> hits){float minDist = attackRange;for (int i = 0; i < hits.Count; i++)if (hits[i].distance < minDist)minDist = hits[i].distance;return minDist;}/// <summary>/// 判断当前对象是否能够与碰撞体发生碰撞。/// </summary>/// <param name="collider">碰撞体对象。</param>/// <returns>true则发生碰撞,false则不发生。</returns>private bool CanCollide(GameObject collider){// 当碰撞体对象是激光时if (collider.TryGetComponent(out LaserWeaponListener weaponListener)){// 同源武器不碰撞if (brickTag == weaponListener.brickTag)return false;}else{// 己方不碰撞if (collider.tag == brickTag)return false;}return true;}#endregion
}
  1. 监听器已经构造好了,但是它只考虑对象构造之后的事情,至于在什么时机下构造对象,则需要额外建立一个类来实现。我们随便构造一个物体当做玩家对象,并在上面新建一个组件,名为“LaserWeaponController”。这个类的主要工作就是:获取玩家的鼠标点击,计算发射者与鼠标点击的射线方向,并将发射者本体的tag和射线方向一并传给LaserWeaponListener,让它执行后续操作。具体代码如下:
using UnityEngine;/// <summary>
/// 激光控制脚本类。
/// </summary>
public class LaserWeaponController : MonoBehaviour
{#region 成员变量[SerializeField] [Tooltip("Prefab模板对象。")] public GameObject prefab = null;private GameObject laserObject = null;                      // 实际激光对象private ObjectPool objectPool = ObjectPool.GetInstance();   // 此处使用了对象池来控制物体的生成#endregion#region 公有方法/// <summary>/// 向对应方向发射激光。/// </summary>/// <param name="ray">射击方向向量。</param>public void Attack(Vector3 ray){// 激光武器只会实例化唯一的对象if (laserObject == null)laserObject = objectPool.GetObject(prefab.name, transform.position, transform.rotation);laserObject.SetActive(true);// 初始化射击方向if (ray.y < 0)laserObject.transform.localEulerAngles = new Vector3(0, 0, Vector2.Angle(new Vector2(-1, 0), ray) + 90);elselaserObject.transform.localEulerAngles = new Vector3(0, 0, Vector2.Angle(new Vector2(1, 0), ray) - 90);// 初始化监听器属性laserObject.GetComponent<LaserWeaponListener>().brickTag = gameObject.tag;laserObject.GetComponent<LaserWeaponListener>().Ray = ray;laserObject.GetComponent<LaserWeaponListener>().IsAttacking = true;laserObject.GetComponent<LaserWeaponListener>().LaserWeaponController = this;}#endregion#region 私有方法/// <summary>/// 在第一帧前触发/// </summary>private void Start(){// 此处采用了先构造对象模板,再通过对象池控制物体的生成和销毁。// 这种方法好处是,如果场景中存在多个角色发射激光,就不会反复的调用Destroy()方法,减少了计算开支prefab = Resources.Load("LASER") as GameObject;}/// <summary>/// 在帧刷新时触发。/// </summary>private void Update(){if (Input.GetMouseButton(0)){Vector3 ray = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;Attack(ray);}}/// <summary>/// 当脚本失效时触发。/// </summary>private void OnDisable(){try{objectPool.ReleaseObject(laserObject);}catch{Debug.Log("Object is already been destroyed!");}}#endregion#region 静态方法/// <summary>/// 获取归一化后的方向向量。/// </summary>/// <param name="ray">射线方向。</param>/// <returns>归一化后的二维向量。</returns>private static Vector2 NormalDirection(Vector2 ray){float dist = Mathf.Sqrt(ray.x * ray.x + ray.y * ray.y);if (dist == 0)return Vector2.zero;return new Vector2(ray.x / dist, ray.y / dist);}#endregion
}

三、附加内容

  • 当完成上述操作之后,只需改变tag就可以得到如下图的效果:
  • 但是此时仍然有一些不足,例如通过压缩y轴尺寸改变激光长度,会使得焰头和焰尾畸变,其实有一个比较简单的解决方案,既使用动态Sprite裁切的方法,直接把超过的部分给删除,而不是改变尺寸;
  • 另外,被攻击物体没有反馈,看起来很单调,这个问题的解决方案就是,在碰撞体的坐标位置添加一个爆炸效果或者烧灼效果的对象,发生碰撞时显示这个对象,不发生碰撞后便隐藏这个对象。

Unity2D游戏开发——制作激光武器(详细流程,附详细注释的代码)相关推荐

  1. 游戏开发制作流程与分工

    游戏开发制作流程详细介绍 一.项目计划阶段 1.创意管理:第一步,是召开会议,在会议中最常见的方法就是采取"头脑风暴法".每个人都必须拿出自己的建议和想法,之后大家一起进行讨论.另 ...

  2. 【Unity2D游戏开发入门第一卷】✨Unity入门总结Sunnyland示例(上卷)

    部分功能例如目录跳转,回到顶部功能在这里有问题 追求阅读体验可以转到 ✨本人主战场!✨ ✨✨目录 一.入门卷 二.杂项卷 三.最后 一.入门卷 回到顶部 前言 准备资源 Tilemap 地图布置,刚体 ...

  3. Unity2D游戏开发和C#编程大师班

    本课程采用现代游戏开发的最新内容和最新技术(Unity 2D 2022) 学习任何东西的最好方法是以一种真正有趣的方式去做,这就是这门课程的来源.如果你想了解你看到的这些不可思议的游戏是如何制作的,没 ...

  4. Unity2D游戏开发基础教程1.2项目、资源和场景

    Unity2D游戏开发基础教程1.2项目.资源和场景 如果使用Unity制作游戏,就一定会接触到项目(Project.资源(Asset)和场景(Scene).本节将依次介绍它们. 1.2.1  项目 ...

  5. Unity2D游戏开发基础教程1.2 项目、资源和场景

    Unity2D游戏开发基础教程1.2 项目.资源和场景 如果使用Unity制作游戏,就一定会接触到项目(Project.资源(Asset)和场景(Scene).本节将依次介绍它们. 1.2.1  项目 ...

  6. unity2d游戏开发系列教程:四、一个2D游戏所需要的主要功能(游戏框架)

    目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 unity2d游戏开发系列教程:三.场景布置,增加怪物和机关 原文下载 ...

  7. 视频教程-丑小鸭历险记——趣味玩转unity2d游戏开发(下)-Unity3D

    丑小鸭历险记--趣味玩转unity2d游戏开发(下) 从业8年以上,学过一点知识,写过一点代码,擅长计算机图形学,擅长unity3d,擅长将抽象的东西讲明白,写看得懂的代码,讲听得懂的课程,不闲聊,不 ...

  8. unity2d游戏开发系列教程:一、环境安装

    从这篇文章开始,一步一步教大家从0开始通过2DGameKit项目进行2D游戏开发 第一步.环境安装 1.先使用手机下载Unity Connect并注册登陆 2.进入unity官网https://uni ...

  9. unity2d游戏开发系列教程:三、场景布置,增加怪物和机关

    目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 第一节.场景草地布置 先查看一下资源文件里都有什么,一会就要用到的 打开 ...

最新文章

  1. 00后的AI开发者进阶之道:从入门到鏖战MIT编程大赛 | 人物志
  2. IPSec ***和SSL ***两种***的安全风险比较
  3. FPGA配置 - 基于SPI FLASH的FPGA多重配置(Xilinx)
  4. 耗时一个月,整理出这份Hadoop吐血宝典
  5. 自动驾驶车辆转向控制(通过支持转角控制的EPS实现角速度控制)
  6. 企业风险定价中的Expected loss估计
  7. python remove函数_Python列表的remove方法的注意事项
  8. 【渝粤教育】国家开放大学2018年秋季 0195-22T机械设计基础 参考试题
  9. xml文件导入wps_电脑中将WPS文档保存为XML格式的方法
  10. EN 45545-2T10水平法烟密度检测的注意事项
  11. 北斗对时服务器(GPS卫星同步时钟)分析北斗与GPS区别
  12. c语言的三个基本语句,C语言-桂林理工大学3-第三章 C程序设计的基本语句.doc
  13. 写给理工科大学生尤其是计算机专业大学生
  14. Windows 文件夹 显示svg缩略图
  15. ueditor(vue-ueditor-wrap)集成秀米全过程以及遇到的问题
  16. # 免费短信测试服务-容联云使用
  17. 英语题目作业(10)
  18. 跟着AI涨知识-量子纠缠
  19. EDD-SPT综合规则
  20. 微信小程序开发教程(一)--注册小程序、下载开发工具及新建工程

热门文章

  1. 羊了个羊1.0(第一关)
  2. 许进生物计算机科学家,计算机专业也能发表JACS?北京大学多学科交叉催生DNA计算取得系列进展...
  3. UWB定位技术的由来
  4. disabled属性
  5. 浅谈AppStore中的评分与评论
  6. Oracle 12C RMAN Cross-Platform Transport of PDBs
  7. React封装日期时间显示组件
  8. idea中的Maven项目怎么配置Tomcat部署
  9. 01、江苏专转本(专业课笔记)第一章、信息技术概述
  10. jdb 调试含package的类