前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity Demo教程系列——Unity塔防游戏(四)弹道(Lobbing Explosives)

Unity Demo教程系列——Unity塔防游戏(四)弹道(Lobbing Explosives)

作者头像
放牛的星星
发布2020-12-11 15:22:26
2.3K0
发布2020-12-11 15:22:26
举报
文章被收录于专栏:壹种念头

目录

  • 1 塔类型
  • 1.1 抽象Tower
  • 1.2 制作特定的塔类型
  • 1.3 特殊类型塔的生成
  • 1.4 迫击炮塔
  • 2 计算轨迹
  • 2.1 水平瞄准
  • 2.2 发射角度
  • 2.3 发射速度
  • 2.4 火力封阻
  • 3 炮弹
  • 3.1 战争工厂
  • 3.2 Game行为
  • 3.3 发射炮弹
  • 3.4 炮弹运动
  • 3.5 清理
  • 3.6 爆炸
  • 3.7 爆炸效果
  • 3.8 平滑爆炸
  • 3.9 炮弹追踪器

本文重点内容: 1、支持更多的防御塔类型 2、创建一个迫击炮塔 3、计算抛物线轨迹 4、发射爆炸弹

这是有关创建简单的塔防游戏的教程系列的第四部分。它增加了迫击炮塔,发射的炮弹会在撞击时爆炸。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。

本教程是用Unity 2018.4.4f1制作的。

(敌人正在承受炸弹轰炸)

1 塔类型

激光并不是我们可以安装在塔上的唯一武器。在本教程中,我们将添加第二个塔类型,该塔类型会在发射撞击时爆炸并损坏附近所有敌人的炮弹。为了使之成为可能,我们必须支持不止一种类型的塔。

1.1 抽象Tower

获取和跟踪目标是任何塔楼都可以使用的功能,因此我们将其放在塔楼的抽象基类中。目前可以简单地使用Tower,但首先将其复制以供以后用作具体的LaserTower。然后从Tower删除所有特定于激光的代码。塔可能不会跟踪特定目标,因此也请删除目标字段,并将AcquireTarget更改为使用Out参数,将TrackTarget更改为使用ref参数。然后从OnDrawGizmosSelected中删除目标可视化,但是保留目标范围,因为这适用于所有塔。

调整重复的类,使其成为LaserTower,它扩展了Tower并使用其基类的功能,从而消除了重复的代码。

然后更新激光塔预制件,使其使用新的特定组件。

(laser塔组件)

1.2 制作特定的塔类型

为了能够选择将哪种塔放置在面板上,我们将引入一个塔类型枚举,就像GameTileContentType一样。我们将支持现有的激光式和稍后创建的迫击炮式。

当我们为每种塔类型创建一个类时,向塔添加一个抽象的getter属性以指示其类型。这与“ 对象管理 ”系列中的形状行为类型相同。

在LaserTower中覆盖它以使其返回正确的类型。

接下来,调整GameTileContentFactory,使其可以生成所需类型的塔。我们将使用Tower数组并添加具有TowerType参数的替代公共Get方法来完成此操作。可以使用断言来验证是否正确设置了数组。另一个公共Get方法现在仅适用于非塔类型瓦片内容。

返回最特定的类型很有意义,因此理想情况下,新的Get方法的返回类型应为Tower。但是用于实例化预制件的私有Get方法返回GameTileContent。我们可以在此处执行强制转换,也可以使私有的Get方法通用。让我们做后者。

由于我们只有激光塔,因此使其成为工厂Tower数组的单个元素。

(塔预制体数组)

1.3 特殊类型塔的生成

要生成特定类型的塔,请调整GameBoard.ToggleTower,使其需要一个TowerType参数并将其传递给工厂。

这就引入了一种新的可能性:在塔已经存在的情况下切换塔,但是它们的类型不同。目前,它只是删除了现有的塔楼,但是将它替换为新类型更有意义,所以让我们来实现吧。这样一来,在发生这种情况时就不需要进行瓦片占用寻路了。

现在游戏需要跟踪什么样的塔应该被切换。我们将简单地将每个塔类型与一个数字关联起来。激光塔为1,这也是默认值,而迫击炮塔为2。按下数字键会选择对应的塔型。

1.4 迫击炮塔

目前,建造迫击炮是失败的,因为我们还没有预制件。首先创建一个最小的MortarTower类型。迫击炮有一个射击速率,我们可以使用每秒发射的配置字段。除此之外,我们还需要一个迫击炮的引用,这样我们才能瞄准它。

接下来,为迫击炮创建一个预制件。你可以通过复制激光塔预制件并更换其塔架组件来实现。然后删除塔和激光束物体。将炮塔重命名为mortar,将其向下移动,使其位于基座顶部,使其略带灰色,然后将其连接起来。同样,在这种情况下,我们可以使用单独的对象来保持迫击炮的碰撞体固定,而仅将碰撞体叠加在迫击炮塔的默认方向上。我将其范围设置为3.5,将每秒发射数设置为1。

(迫击炮塔预制件)

为什么它被称为迫击炮? 该武器的最早版本基本上是铁碗,看起

将炮塔预制加入到工厂的数组中,这样就可以将迫击炮塔放置在游戏板上。现在,他们还没有做任何事情。

(两种塔类型,一个没激活)

2 计算轨迹

迫击炮是通过以一定角度发射弹丸来工作的,因此它会越过障碍物并从上方击中目标。通常,使用的炮弹会在撞击时或仍高于目标时爆炸。为了简单,我们将始终瞄准地面,因此一旦炮弹的高度降低到零,它们就会爆炸。

2.1 水平瞄准

要让炮塔瞄准,你首选要将其水平指向目标,然后调整其垂直方向,使炮弹以正确的距离着陆。我们从第一步开始,首先使用固定的相对点而不是移动目标,以轻松验证我们的计算是否正确。

将一个GameUpdate方法添加到MortarTower中,该方法始终调用Launch方法。现在,我们将使用可视化所涉及的数学,而不是启动实际的shell。发射点是炮塔在世界上的位置,该位置略高于地面。将目标点沿X轴进一步放置三个单位,并将其Y分量设置为零,因为我们一直瞄准地面。然后通过调用Debug.DrawLine在它们之间画一条黄线来显示这些点。该线将在场景视图中显示一帧,这是足够的,因为我们每帧绘制一条新线。

(目标是一个相对固定的点)

使用这条线,我们可以定义一个直角三角形。它的最高点位于炮身的位置。相对于迫击炮[0,0]。塔底的下面的点是[0,y],目标点是[x,y],当x是3,并且y为负的垂直位置是炮塔的着陆点。 我们需要跟踪这两个值。

(目标三角形)

通常目标可以在射程内的任何位置,所以Z维也起作用。但是,目标三角形仍然是2D的,它只是绕着Y轴旋转。为了说明这一点,我们将添加一个相对偏移向量参数,用四个XZ偏移量启动和调用它:[3,0],[0,1],[1,1],和[3,1]。目标点等于发射点加上它的偏移量,然后它的Y坐标被设为零。

现在目标三角形的x等于从塔底指向目标点的2D向量的长度。将这个向量归一化也会得到一个XZ方向向量我们可以用它来对齐三角形。通过用一条白线画出三角形的底部来说明这一点,这条线是从方向和x派生出来的。(对齐目标的三角形)

2.2 发射角度

下一步是计算出炮弹必须发射的角度。我们需要从炮弹轨迹的物理推导出来。不考虑阻力、风或任何其他干扰,只考虑发射速度v和重力g=9.81 。

弹体的位移d与瞄准三角形对准,可以用两个分量来描述。水平位移很简单,,其中t 是发射后的时间。垂直分量是相似的,但也受负加速度由于重力,所以

位移是如何计算的? 公式太难截图了,看原文吧。。。

(推导发射速度)

y的是怎样推导的呢

tanθ的推导是怎样的呢?

有两种可能的发射角度,因为可以瞄准低或高。低轨迹越快,越接近目标直线。但是高轨迹在视觉上更有趣,因此我们将使用这种方案。这意味着我们只需要使用最大的解决方案,计算完成后,还有cosθ和sinθ,因为我们需要这些来推导发射速度矢量。我们需要通过math.atan将角度转换成弧度。先用固定的发射速度5。

让我们通过画十个覆盖飞行第一秒的蓝色线段来可视化轨道。

(抛物线飞行轨迹为一秒)

这两个最远的点可以在不到一秒的时间内到达,所以我们可以看到它们的整个轨迹,这条线在地下继续延伸一点。另外两个点需要更大的发射角度,这将导致更长的轨迹,需要超过一秒的时间才能穿过。

2.3 发射速度

如果我们想在一秒钟内到达最近的两个点,那么我们就必须降低发射速度。让我们将其设置为4。(发射速度减少为4)

它们的轨迹现在也完成了,但是另外两个消失了。这是因为现在的发射速度不足以达到这些点。在这些情况下,没有解决方案,这意味着我们最终得到一个负数的平方根,导致非正常的值,这导致我们的线消失。可以通过检查r 是否为负来检测。

我们可以通过使用足够高的启动速度来避免这种情况。但是,如果它变得太高,那么附近的目标将需要很高的轨迹和飞行时间才能击中,因此我们要保持尽可能低的速度。我们的发射速度应该足以达到最大范围的目标。

在最大射程,r=0所以对于tanθ来说,只有一个解,这是一个低轨迹。这意味着我们知道了所需的发射速度

s是如何进行推导的?

当迫击炮唤醒或在游戏模式下调整其范围时,我们只需要计算出所需的速度即可。因此,请使用字段跟踪它并在Awake和OnValidate中对其进行计算。

但是,由于浮点精度问题,非常接近最大范围的目标可能会失败。因此,在计算所需速度时,我们应该在范围内添加少量的补充值。而且,敌人的碰撞体半径有效地扩展了最大塔范围。由于敌人的缩放,我们将其设置为0.125,最多增加一倍,因此将有效范围再增加0.25,例如0.25001。

最后,在Launch中使用启动速度。(使用推导的速度,目标半径为3.5)

2.4 火力封阻

现在我们的轨迹计算是正确的,我们可以摆脱相对固定的测试目标。相反,应该为Launch提供一个目标点。

炮塔并不会每一帧都开火。跟踪发射进度,就像敌人的生成进度一样,并在GameUpdate发射时获取随机目标。但是那时可能没有目标可用。在这种情况下,我们将保持启动进度,但不要让它进一步累积。实际上,为防止无限循环,我们应将其设置为略小于1。

我们不跟踪发射间隔之间的目标,但我们必须在发射时正确对齐迫击炮。我们可以使用四元数来使用水平发射方向矢量来水平旋转迫击炮。我们还需要考虑发射角,通过对方向向量的Y分量使用tanθ和Y。这是可行的,因为水平方向的长度是1,因此 tanθ =sinθ。

(旋转矢量分解)

为了仍然能够看到发射轨迹,我们可以在Debug.DrawLine中添加一个参数来为其提供持续时间。

(对目标进行火力封阻)

3 炮弹

计算轨迹的关键在于我们现在知道了如何发射炮弹。下一步是创建并启动它们。

3.1 战争工厂

我们需要一个工厂来创建炮弹对象的实例。发射到空中后,炮弹会自行存在,不再依赖发射炮弹的迫击炮。因此,迫击炮塔不应该管理它们,游戏瓦片内容工厂也不适合。让我们为与武器相关的所有事物创建一个新工厂,将其命名为war factory。首先,使用适当的OriginFactory属性和Recycle方法创建一个抽象的WarEntity。

然后为我们的炮弹创建一个具体的Shell war实体。

其次是WarFactory本身,它可以通过公共getter属性传递Shell。

为Shell创建一个预制件。我只是简单地使用0.25缩放和深色材质的立方体,再加上Shell组件。然后创建war factory资产并将外壳预制件分配给它。

(war factory)

3.2 Game行为

要移动shell,我们必须对其进行更新。我们可以使用Game用于更新敌人的相同方法。实际上,我们可以通过引入抽象的GameBehavior组件(扩展MonoBehaviour并添加虚拟GameUpdate方法)来使这种方法通用。

然后重构EnemyCollection,将其转换为GameBehaviorCollection。

使WarEntity扩展GameBehavior而不是MonoBehavior。

并为Enemy做同样的事情,现在覆盖GameUpdate方法。

从现在开始,Game必须跟踪两个集合,一个集合用于敌人,另一个集合用于非敌人。非敌人应在其他所有内容之后进行更新。

更新Shell的最后一步是以某种方式将它们添加到非敌人的集合中。通过将Game功能用作war factory的静态外观来实现,因此可以通过调用Game.SpawnShell()来生成shell。为了使这项工作发挥作用,游戏需要引用war factory,并且必须跟踪自己的实例。

(Game和War factory)

静态外观是一种好方法吗? 这是一种方便的工具,可以处理可能被很多东西(例如Shell)在任何地方生成的简单东西。

3.3 发射炮弹

产生炮弹后,炮弹必须沿着其轨迹飞行,直到到达目标为止。为此,请向Shell添加一个Initialize方法,并使用它来设置其发射点,目标点和发射速度。

现在我们可以在MortarTower.Launch中生成一个shell并按其方式发射它。

3.4 炮弹运动

为了使Shell行动起来,我们必须跟踪它的诞生时间,这是自发射以来的时间。然后,我们可以计算其在GameUpdate中的位置。我们始终相对于其发射点执行此操作,因此无论更新频率如何,它都能完美地遵循其轨迹。

(发射炮弹)

为了同时使炮弹与它们的轨迹对齐,我们必须使它们沿着导数向量方向,这就是当时的速度。

(旋转炮弹)

3.5 清理

既然已经清楚了炮弹正在按其应有的状态飞行,我们就可以从MortarTower.Launch中删除轨迹可视化。

同时,我们必须确保炮弹一旦击中目标,就不再存在。因为我们总是瞄准地面,所以我们可以在Shell.GameUpdate中检查垂直位置是否被减少到零或更少。可以在计算它之后,在调整炮弹的位置和旋转之前,直接做这个。

3.6 爆炸

我们发射炮弹是因为里面装满了炸药。当一枚炮弹击中目标时,它会引爆并伤害爆炸区域内的所有敌人。爆炸半径和伤害程度取决于迫击炮发射的炮弹种类,所以增加了迫击炮塔的配置选项。

(炮弹半径1.5 伤害为15)

此配置仅在炮弹爆炸时才重要,因此需要将其添加到Shell及其初始化方法中。

生成数据后,MortarTower只需将数据传递到Shell。

要击中射程内的敌人,炮弹必须获得目标。我们已经有了代码,但它现在在Tower里。因为它对于任何需要目标的东西都很有用,所以将该功能复制到TargetPoint并使其静态可用。添加一个方法来填充缓存区,一个属性来获取缓存计数,以及一个方法来获取缓冲目标。

现在,我们可以获取范围内的所有目标(最大缓存区大小),并在炮弹爆炸时损坏它们。

(炮弹击中)

我们还可以向TargetPoint添加静态属性,以方便地获取随机缓存的目标。

3.7 爆炸效果

一切都完美运作了,但看起来还没有说服力。我们可以通过在炮弹爆炸时添加爆炸的可视化效果来增强这一点。除了看起来更有趣之外,它还为播放器提供了有用的视觉反馈。为此,我们将创建一个类似于激光束的爆炸预制件,除了它是一个球体之外,它更透明,并且颜色更亮。给它一个新的,具有可配置持续时间的爆炸战争实体组件,合理的默认时间为半秒,该组件很短但也可以清楚的表现了。

给它一个Initialize方法来设置它的位置和爆炸半径。设置比例时,我们需要将半径加倍,因为球体网格的半径为0.5。这也可以对范围内的所有敌人施加伤害,因此它也应该有一个伤害参数。除此之外,它还需要一个GameUpdate方法来简单地检查时间是否到了。

将Explosion添加到WarFactory。(War factory 和 explosion)

并为它添加一个facade方法到游戏中。

现在,炮弹可以在达到目标时生成并初始化爆炸。爆炸可造成伤害。

(炮弹爆炸)

3.8 平滑爆炸

使用不变的球体进行爆炸看起来很糟糕。我们可以通过设置其不透明度和缩放比例来改善这种情况。我们可以为此使用简单的公式,但是让我们同时使用它们的动画曲线,以便于对其进行调整。为此,将两个AnimationCurve配置字段添加到爆炸中。我们将使用曲线来配置爆炸生命周期内的值,时间1代表爆炸的结束,无论其实际持续时间如何。比例和爆炸半径也是如此。这使得配置更加容易。

我将不透明度的开始和结束位置设置为零,并在中间点平滑地放大到0.3。使比例从0.7开始,然后迅速增加,然后慢慢接近1。

(爆炸曲线)

使用材质属性块来设置材质的颜色,即具有可变不透明度的黑色。现在已在GameUpdate中设置了比例,但是我们需要使用字段来跟踪半径。可以在Initialize中将缩放比例加倍一次。通过使用当前age除以爆炸持续时间作为参数调用Evaluate来找到曲线值。

(动画化后的爆炸物)

3.9 炮弹追踪器

由于炮弹很小并且具有相对较高的速度,因此很难看到它们。当查看单个帧的屏幕截图时,轨迹根本不清楚。通过在炮弹上添加尾迹效果,可以使这一点更加明显。对于普通的炮弹来说这是不现实的,但是我们可以声明它们是示踪剂炮弹。为了使弹道清晰可见,专门制造了此类弹道。

创建追踪器的方法有很多种,但是这里我们将使用一种非常简单的方法。重新调整爆炸的用途,让炮弹每帧生成一小片。这些爆炸不会造成任何伤害,因此获取目标将是不必要的。让Explosion通过仅施加大于零的损害来支持这种美容用途,然后将Initialize的损害参数设为可选。

在Shell.GameUpdate的最后以小半径(例如0.1)产生爆炸,以将它们变成示踪炮弹。请注意,这种方法每帧产生一次爆炸,因此帧速率也取决于它,这对于这种简单的效果是很好的。

(炮弹拖尾)

下一章,剧情。

欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。

本文翻译自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 壹种念头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档