|
200+篇教程总入口,欢迎收藏:
本文重点内容:
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的系列教程
原文地址: |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|