找回密码
 立即注册
查看: 571|回复: 0

[简易教程] Unity基础教程-对象管理(十一)——生命周期(Growth and Death)

[复制链接]
发表于 2020-11-30 10:18 | 显示全部楼层 |阅读模式
200+篇教程总入口,欢迎收藏:
本文重点:
1、让形状增大和缩小
2、允许杀死形状的行为
3、延迟到Game Update循环之后杀死
4、用缩小代替形状的立即销毁
这是有关对象管理的系列文章的第11个教程。通过增加生长和死亡的行为,它引入了更多的行为来丰富形状的生长和销毁。
本教程是CatLikeCoding系列的一部分,原文地址见文章底部。“原创”标识意为原创翻译而非原创教程。
本教程使用Unity 2017.4.12f1编写。
限制生命周期来保障人口稳定
1 形状增长
无论何时生成一个形状,它都会立即以全尺寸出现。形状在没有任何预兆的情况下突然出现,这可能是一种不和谐的体验。一个使新形状的引入更加平滑和渐进的方法是让它们初始的缩放比例为零,然后慢慢地将它们增长到完整大小。另一种方法是首先让它们完全透明,然后逐渐让它们不透明。当然也可以同时进行这两个行为,或者同时做别的行为。我们不必把自己限制在一个单一的方法上,所以最灵活的方法是创建一个行为,而不是将其构建到Shape类中。在本教程中,我们将使用第一种方法:增长。
1.1 增长行为
要支持不断增长的形状,请在ShapeBehaviorType枚举中添加一个Growing选项。
将相应的情况添加到返回GrowingShapeBehavior的GetInstance方法中。
为新的GrowingShapeBehavior类创建一个基本实现。
1.2 从0到1的缩放
GrowingShapeBehavior的目的是将形状从零增加??到我们最初给它的比例。 因此,我们必须追踪字段中的原始比例。 另外,它需要一段时间才能成长,因此我们还需要一个时间字段。 这两个值也必须能保存和加载。
我们的想法是把这个行为添加到一个已经有最终缩放的形状上。我们将通过一个Initialize方法来配置行为,在这个方法中,可以检索原始的比例并通过一个参数提供持续时间。然后我们将形状的比例设置为零。
在GameUpdate中,只要形状的Age小于增长持续时间,就需要调整形状的比例。比例因子是年龄除以持续时间。当形状足够老之后,将恢复为其原始比例,并且不再需要这种行为。
1.3 配置增长
生长阶段的持续时间会根据生成区配置的时间。像卫星选项一样,我们将在SpawnZone.SpawnConfiguration中定义一个嵌套的LifecycleConfiguration结构,以便将与形状生命周期相关的所有选项进行分组。现在这只是持续时间,但我们稍后会添加更多参数。生长持续时间可以是随机的,但不应该太长,例如介于零到两秒之间。
生长将持续1~2秒
向SpawnZone添加一个方法来设置一个形状的生命周期。除了形状参数外,还添加一个参数,用于期望的增长持续时间。如果持续时间大于零,则向该形状添加一个GrowingShapeBehavior。否则我们就不需要为行为操心了。
我们把持续时间的增长作为一个参数,这样就可以对一个形状和它的所有卫星使用相同的持续时间。要使其生效,需要给CreateSatelliteFor添加一个持续时间的参数,并让它在最后为卫星形状调用SetupLifecycle。
在SpawnShapes结束时,随机确定增长持续时间,并将其传递给所有卫星。在卫星创建后,我们可以建立主要形状的生命周期。但这不能太早去做,因为卫星的尺度取决于焦点形状的尺度。初始化增长行为会将比例设置为零,因此必须延迟。
1.4 平滑增长
当使用生长行为时,形状不再立即出现。但是这种增长是线性的,所以玩家并不知道一个形状何时完成增长。生长阶段会在某个任意时刻停止。但我们可以使它更光滑,可以使用
,代替线性s比例因子。这就是所谓的平滑曲线。
线性和平滑曲线
差异是细微的,但是形状现在会变化,它们的增长速度,开始时的慢速,中途的最快以及在接近完成时会再次减速。
2 形状消亡
如果我们支持不断增长的形状,那么支持逐渐消失的形状也不是很困难。濒临死亡的形状不会扩大其比例,而是会收缩,直到其缩放比例降为零。
2.1 销毁行为
将Dying选项添加到ShapeBehaviorType,并将相应的情况添加到GetInstance方法。然后通过复制和重命名GrowingShapeBehavior并根据需要调整BehaviorType属性和Recycle方法来创建DyingShapeBehavior类。此类调整现在应该是频繁的,因此这里就不明确显示它们了。
濒死的行为需要原始的缩放和持续时间,就像成长一样。但是成长假设我们从Age为0开始,而死亡可以从任何Age开始。因此,我们还需要跟踪开始死亡的年龄,即调用Initialize的Age。另外,由于我们正在缩小,因此在Initialize 中不应将原始比例设置为零。
GameUpdate只需要稍作修改。通过从形状的当前Age中减去死亡Age来找到死亡持续时间。最终比例为零。然后将标量反转,这是通过在平滑之前使用1减去持续时间除法作为初始标量来完成的。
2.2 配置死亡
死亡持续时间的长短也是我们将在每个生成区域中配置的内容,因此请将其字段添加到LifecycleConfiguration中,并使用与生长持续时间相同的范围。
因为我们现在必须为生命周期确定两个持续时间,让我们向LifecycleConfiguration中添加一个方便的属性,它将同时返回两个随机的持续时间,作为Vector2的第一个组件是growing,第二个组件是dying。
我们不是应该使用自定义结构而不是Vector2吗?
如果该函数是公开可用的,并在我们项目的其他部分使用,那将是一个好主意。但是我们在SpawnZone中只使用了一次,因此向量也不错。
更改SetupLifecycle,以便它使用这样的向量作为其参数,而不是再使用单个持续时间。为了快速独立地测试我们的濒死行为,可以让它增长或增加濒死行为,但不要同时使用两者。只有当我们有死亡的持续时间而没有增长的持续时间时,才应该添加死亡的行为。
调整CreateSatelliteFor,使其使用向量。
并更新SpawnShapes。
2.3 杀掉形状
当仅使用濒临死亡的行为时,我们将看到形状突然出现,并立即开始缩小并消失。但是,即使他们的比例缩小到零,他们仍然活着。形状的数量会持续增加,直到达到最大值(如果已设置),此时形状将被随机破坏。
濒死行为的关键是形状的缩放达到零时应该死亡。为了支持这一点,我们需要让Game以外的其他类来杀掉形状。因此,向Game添加带有形状参数的公共Kill方法。就像销毁形状时一样,获取其保存索引,回收形状,将最后一个形状移到列表中,然后删除列表中的最后一个元素。
现在我们可以通过调用Game.Instance.Kill(shape)杀死任何地方的形状,但是我们也可以在Shape中添加一个方便的Die方法使其更容易。
这样就可以在结束时直接在DyingShapeBehavior.GameUpdate中调用shape.Die(),而不是将scale设置为零。但是,由于形状被回收(回收了其所有行为),因此我们不再需要指示其删除行为。因此,返回true而不是false。
2.4 延迟杀死
虽然死去形状这时确实被删除了,但这是在游戏运行它的形状列表时杀死了的。这将导致形状列表的顺序发生变化,列表中的最后一个形状移动到当前正在更新的索引中。这样做的一个结果是这次Update跳过了打乱的形状。虽然打乱形状更新的顺序并不重要,但我们必须确保它们总是得到Update。
为了在问题即将发生时发现问题,我们首先需要知道Game当前是否正在通过其形状列表进行工作。为此,可以添加一个布尔值字段以指示我们当前是否处于游戏更新循环中。紧接循环之前将其设置为true,紧接循环之后将其设置为false。
如果我们在循环内部,那么一定不能打乱列表。如果一个形状被杀死,那么它从列表中删除的时间必须被推迟。可以通过将杀死的形状添加到一个单独的kill列表中来实现这一点,除了常规的形状列表之外,还必须追踪这个列表。
现在Kill可以检查我们是否处在游戏更新循环中。如果是的话,将形状添加到删除列表中。否则,立即杀死形状。将原始的kill代码移到一个单独的KillImmediately方法中,该方法应该是私有的。
在FixedUpdate结束时,检查kill列表中是否有任何形状。如果是,立即将它们全部杀死,然后清除列表。
我们还可以在DestroyShape中立即使用KillIl,从而消除重复的代码。
2.5 阻止多余的Kill
延迟杀死方法可以确保所有形状都按需更新,但这带来了另一个潜在问题。现在,相同的形状有可能被杀死多次。例如,濒死的行为有可能杀死形状,然后由于形状限制而立即被破坏。也许还有其他行为可能随时杀死任何形状。
我们必须避免在一个形状已经死亡的时候再次杀死它,因为那样会导致它在不应该被回收的时候被回收。甚至它已经被回收了,这将导致它被收集两次,从而导致之后潜在的麻烦。我们可以通过将kill列表转换为一个形状实例列表,并在kill之前检查它们是否仍然有效来防止所有这些问题。
3 完整的生命周期
我们有一种成长的行为和一种死亡的行为。如果我们把它们放在一起,在它们之间有一段完整的生命,我们就得到了一个完整的生命周期。可以通过创建一个包含所有增长和死亡代码的单一行为来实现,但也可以继续使用我们已经拥有的行为,加上一个额外的生命周期行为,在需要时添加其他行为。现在,在当前的例子中可能有点多余了,但却不失为一个有趣的尝试方法,所以我们会去尝试一下。
3.1 生命周期行为
创建一个新的LifecycleShapeBehavior,链接到Lifecycle枚举选项。从DyingShapeBehavior的副本开始,然后进行所需的更改。
生命周期包括成长,成年和死亡三个阶段,每个阶段都有自己的持续时间。生长阶段会立即开始,因此Lifecyclebehavior可以根据需要立即在Initialize中添加所需的行为。这意味着它不需要跟踪自己领域中的生长持续时间,只需将持续时间传递给生长行为即可。它也不需要知道原始比例。但它需要跟踪成年持续时间和濒死持续时间。除此之外,濒死的年龄等于成长的持续时间加上成年的持续时间。
在GameUpdate中,生命周期仅需要检查形状是否已达到濒死的年龄。发生这种情况时,它会增加濒临死亡的行为并自行消除。这也许会触发得太晚了,因此,通过增加死亡年龄并减去当前年龄,可以减少损失的时间来缩短最终的死亡时间。
实际上,死亡行为只有在存在持续时间的情况下才需要。如果没有,形状可以立即终止,生命周期行为不需要显式删除。
3.2 配置生命周期
要配置完整的生命周期,请将成年持续时间添加到LifecycleConfiguration中。它的Durations属性变为Vector3,用成年持续时间代替快逝的持续时间,后者移到第三部分。
我们只需要在SpawnZone.SpawnShapes中更改矢量类型。
以及CreateSatelliteFor。
SetupLifecycle开始变得有点复杂了。如果存在一个不断增长的持续时间,那么,如果我们至少有一个其他持续时间,就需要一个完整的生命周期。否则,只需要增长时间。如果我们有一个成年的持续时间,那么我们也需要一个生命周期。最后,完成死亡的行为。请确保更改代码,以便它使用向量的第三个组件。
3.3 不同卫星不同生命周期
当前,形状及其所有卫星具有相同的生命周期,但这不是必需的。让我们在卫星配置中添加一个切换选项,以控制生命周期是否统一。
在统一生命周期的情况下,我们继续使用相同的方法。否则,我们将为每颗卫星使用一组新的随机持续时间。
如果焦点形状首先消失的话,生命周期造成的死亡可能会导致卫星逃逸。
4 缓慢的销毁
杀死形状会使它们收缩然后死亡,而不是立即消失。但是,当某个形状被销毁时(无论是由玩家还是由于形状过多),它们仍然会立即消失。我们可以更改此类销毁,以使它们也逐渐减少,但这需要格外小心。
如果破坏形状只是杀死它们的另一种方式,那么我们就不应该去破坏已经死亡的形状。由于死亡形状已经过时,所以在检查形状限制时不考虑它们是有意义的。要做到这一点,就必须能够区分正在消失的形状和没有消失的形状。
区分的一种方法是将所有濒死的形状放在一个单独的形状列表中,然后从常规形状列表中删除它们。然后我们自动忽略死亡形状时,选择一个随机的破坏和检查限制。但是,这会影响保存的索引和我们操作形状列表的所有位置,因为我们将有两个列表而不是一个。
另一种区分的方法是通过形状列表的顺序。我们可以将列表分割为两部分,有效地处理两个列表,同时处理单个列表的所有代码仍然有效。这将使我们必须做出的改变最小化,所以我们将使用这种方法。
濒死的和存活的形状在一个列表中
4.1 分离濒死形状
通过将所有濒临死亡的形状移到列表的前面,我们可以将形状列表划分为濒临死亡和非临死的部分。因为这是一个列表顺序操作,所以我们必须小心。向Game添加一个私有MarkAsDyingImmediately方法,以在即将死亡的区域中放置一个形状。跟踪字段中濒死的形状计数,并将其用作标记为濒死的形状的新索引,将位置与该索引处的形状交换。然后,增加濒死形状的数量。
将形状标记为多次死亡没有意义,因此,如果形状已经在即将死亡的区域中,则将其终止。当其索引低于濒死的形状计数时就是这种情况。
此更改会立即影响KillShape。首先,如果要删除的形状消失了,我们必须减少濒死计数。其次,我们不能再盲目地将最后一个形状移到移除形状的索引上。这样做可能会在濒死区域中放置非濒死形状。换句话说,当形状的索引小于濒死计数并且还小于濒死计数减一时。在这种情况下,我们必须执行两次移动:将最后一个即将消失的形状更改为已删除的形状,并将列表中的最后一个形状更改为已创建。
杀掉濒死的形状需要移动2次
这个条件命题是如何运作的呢?
如果我们要处理的是濒死的形状,则第一个条件的评估结果为true。只有在这种情况下,才会评估第二个条件,该条件首先减少濒死计数,然后执行其他比较。你也可以将其变成两个嵌套的if块:
但只有在至少有一种不濒死形态的情况下,才有可能采取双重行动。如果没有,我们创建的Item就在列表的末尾,所以我们根本不需要移动最后一个形状。因为不需要在列表的末尾填充一个Item,所以可以跳过这一步。
现在我们知道了濒死的形状数,在检查是否已超过FixedUpdate中的限制时,从形状数中先减去它。这使得它仅适用于非濒死形状。因此,形状的总数可能会超过限制,直到所有濒死的形状都消失为止。
同样,在DestroyShape中,我们仅在存在非濒临死亡的形状时才继续前进,然后仅从列表的第二部分中选择随机形状。
最后,请确保每次我们开始新游戏时将濒死的形状计数设置为零。
4.2 延迟标记
因为将形状标记为濒死会改变形状列表的顺序,所以我们必须确保在游戏更新循环中不会发生这种情况。可以使用与kill列表相同的方法,因此为需要标记的形状添加第二个列表。
在FixedUpdate结束时循环这个列表,立即标记那些仍然有效的元素。
现在我们可以添加一个公共的MarkAsDying方法,它可以向列表中添加一个形状,也可以立即对其进行标记。
我们还可以向Shape添加另一种便捷的方法。
我们唯一需要调用这个方法的地方是在初始化一个DyingShapeBehavior时。
如果还有其他表现出不同死亡方式的行为,那么这些行为也应在初始化期间将其形状标记为死亡。
4.3 缓慢销毁
为了最终支持缓慢销毁,我们需要决定销毁的持续时间。通过向Game中添加一个可序列化的字段,使其可配置。
当持续时间为正时,让DestroyShape在具有该持续时间的形状上添加一个濒死行为,而不是立即杀死它。
销毁设置为1秒,总数量设置为20
4.4 避免双重濒死
形状的销毁与它们的生命周期无关。这意味着,一次随机的销毁可能会在一个仍在增长的形状上增加濒死的行为。这不是问题,因为死亡行为是后来添加的,所以覆盖了增长行为的规模变化。但麻烦的是,即使已经添加了一个,生命周期里仍然可以再次添加一个濒死行为。第二个行为会启动一个新的收缩效果,该效果会覆盖第一个,但最先完成的会决定何时kill该形状。
为了防止向一个形状添加第二个濒死行为,必须能够检查该形状是否已经濒死,当然无论原因是什么。可以在游戏中添加IsMarkedAsDying方法来检查这个。它所要做的就是检查形状的索引是否小于死亡计数。
我们再次通过Shape方便地实现了这一点,不过这个时候,readonly属性是比较合适的。
最后,在LifecycleShapeBehavior。当它到达死亡年龄时,检查它是否已经死亡。如果是,不要添加濒死行为。
下一个教程是 更复杂的关卡
本文翻译自 Jasper Flick的系列教程
原文地址:
https://catlikecoding.com/unity/tutorials

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-21 04:45 , Processed in 0.100751 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表