游戏引擎学习第62天

devtools/2024/12/29 4:00:48/

回顾

我们目前正在开发一把虚拟剑,目的是让角色可以用这把剑进行攻击。最初的工作中,我们使用了一个摇滚位图作为虚拟剑的模型,并且实现了一个基本的功能:角色可以丢下剑。但这个功能并没有达到预期的效果,因为我们想要的行为是角色能够投掷剑,而不是简单地放下它。

接下来的目标是完善这一功能,使得剑能够像《塞尔达传说》中的剑一样被投掷出去。为此,我们计划进一步修改代码,使得剑不仅仅是一个物品,而是一个有效的抛射物,可以像其他游戏中的投掷武器一样被投掷并做出合理的反应。

在这个过程中,我们发现目前的实现有些荒谬,因为丢下剑的方式并没有达到预期的投掷效果。因此,接下来的工作是使得这一行为更加合理,确保剑可以像项目物一样被投掷并且能合理地与其他实体互动。昨天我们完成了大部分工作,接下来只需要继续调整,使这个功能顺利实现。

game.cpp:让剑实际被投掷出去

首先,我们讨论了如何让剑出现在指定位置。接下来,我们考虑了如何给剑添加速度,使其能够在某个方向上飞行。这是通过将剑的速度设置为投掷方向来实现的,目的是让剑能够移动一段距离。

为了限制剑的飞行距离,我们考虑了设置一个最大飞行距离,这样剑不能无限远地飞行。比如,设定剑只能飞行五米,超过这个距离后,剑就停止移动。为了实现这一点,我们在实体中添加了一个“剩余距离”属性,并初始化为一个合理的数值,比如五米。

此外,为了管理剑的速度,剑需要被添加到高优先级实体集合中,确保它能够受到控制并参与到游戏的物理系统中。虽然我们还没有具体操作,但设置剑的速度和管理它的实体位置仍然是接下来的工作。
在这里插入图片描述

讨论令人烦恼且丑陋的实体使用方式

目前,虽然低端部分的实现对功能没有问题,但在代码结构上存在一些烦人的地方,尤其是涉及指针和索引的部分,造成了大量的打字工作,且代码的可读性变差。虽然这些问题并没有引发错误,但打字过多和结构不简洁使得开发进程变得缓慢,并且影响了代码的清晰度。因此,考虑到这一点,计划将这些结构简化,使代码更标准化,减少冗余和不必要的复杂性,从而提升代码的可读性和维护性。虽然目前没有遇到严重的错误,但这种冗余的打字和代码结构仍然需要在未来的开发中进行优化和改进。

在这里插入图片描述

回到实现剑的投掷功能

目前,已经决定将剑作为高频更新实体,并且需要设置其速度。预计剑的速度为每秒两米,尽管这一数值可能会根据需要进行调整。接下来,需要确保剑在发射后朝预定方向移动,但目前尚未实现实体的实际更新。进一步检查发现,实体的移动功能尚未完全实现。接下来需要在代码中加入适当的更新机制,使得剑能够按照设定的速度和方向正确移动。

在这里插入图片描述

game.cpp:UpdateSword

在更新过程中,剑实体并不会自动更新位置,除非明确调用更新函数。当前的设计中,剑实体没有单独的更新调用,这意味着它不会自行移动。为了使剑能够移动,必须在适当的时候调用“更新实体”函数。

这部分代码设计考虑了是否需要特殊处理剑实体的更新。虽然可以通过类似投射物的通用更新方法来处理剑,但当前的目标是逐步确定实体系统的工作方式。因此,当前所有的更新都显式写出,并根据实际情况进行优化,以避免在游戏构建中出现不合适的基础设计。

目前,剑的移动并没有涉及加速度,保持恒定速度。类似塞尔达风格的剑通常会维持恒定速度。在这种情况下,基本上只需要调用已有的“移动实体”函数,该函数应该能正常工作,尽管目前还需要进一步检查是否有其他问题。
在这里插入图片描述

仅在当前阶段,如果实体不发生碰撞,就关闭碰撞检测

在当前的实现中,需要对剑的碰撞检测进行暂时关闭。首先,将剑标记为不进行碰撞检测,以便它能够自由地穿过物体。在未来,会对这种行为进行进一步处理。通过这种方法,可以使得被标记为非碰撞的剑能够顺利穿过其他物体。对于暂时关闭碰撞检测的具体操作,如果发生碰撞,才会进行碰撞测试,否则不会进行任何测试。这种做法是为了更好地处理剑的行为,并让它在游戏中表现得更加灵活。接下来,计划完成相关代码,确保能够更新剑的状态,并传递游戏状态和实体信息。

在这里插入图片描述

运行游戏,发现剑减速了

目前,剑的运动效果并不令人印象深刻,尽管剑确实移动了,但它的速度正在减慢。看起来问题仍然存在,且这正是一个好时机来解决这个问题。事实上,之前并未完全解决与“Familiar”相关的部分,因此现在是时候将这些问题抽象出来,进行进一步处理和修复。

把移动概念分离到 move_spec 中,并引入 DefaultMoveSpec

目前存在一个问题,即玩家的速度、加速度、阻力等参数都被内置在系统中,这样每个对象都无法根据自己的需求调整这些参数。尤其是对于像剑这样的物体,它们不应该受到与玩家相同的空气阻力影响,因此需要对这些参数进行调整。

为了解决这个问题,首先要做的是确保加速度不再与玩家的速度直接相乘,因为这样是不正确的。加速度应该是由调用者传入的有效值,MoveEntity 只负责根据这些加速度响应运动。

此外,还需要对阻力系数进行调整,确保其也可以作为参数传入。这个参数会影响加速度,并且需要在运动方程中处理。为了方便管理这些参数,可以引入一个 (move_spec),其中包括加速度和阻力系数。这样,我们就可以通过 MoveEntity 函数传入这些参数,从而灵活控制运动和加速度。

具体来说,添加一个默认的 (move_spec) 作为参数,可以让对象的加速度和阻力值按照需求初始化,而不是硬编码在每个对象中。例如,剑的初始速度为零,且没有阻力,这样它就能自由地移动而不受其他物体影响。

这种方式的好处是,它使得每个对象的物理行为更加灵活和可配置,不同的对象可以根据自己的需求调整加速度和其他运动参数。最终,系统会支持通过这种“移动规格”参数来控制运动行为,确保物理模拟更符合实际需求。

在这里插入图片描述

运行游戏,发现剑依然很慢

验证了一下,系统中的所有功能似乎都在正常工作。熟悉的物体(头部)仍然在跟随我们,这是预期的结果。我们的剑(或所谓的剑)也成功地掉落,表现如我们所愿。然而,剑的移动速度看起来有些慢,这可能需要进一步优化或调整,至少可以说,速度表现并不理想。

加快剑的速度,并确保剑只能在特定距离内移动

我们可能需要确保剑的速度在未来可以稍微提高一些。接下来,我们的目标是实现一个功能,让剑在一定距离后消失。首先,我们需要更新一些参数,并设置一个剩余距离变量,作为一个计时器。通过计算实体当前的位置与其原始位置的差值,我们可以得出实体已移动的距离。

当剩余距离减少到零时,剑应该消失。因此,我们需要更新剑的实体位置,使其不再存在,实际上是将其位置设置为“无处可去”。这可以通过更改实体的位置来实现。

此外,我们还需要解决一个问题,就是我们现在缺少一个准确计算矢量长度的函数。目前我们有计算长度平方的功能,但缺少计算实际长度的部分。这并不复杂,因为我们可以通过对长度平方结果进行平方根运算来得到实际长度。

值得注意的是,矢量长度的计算中,两个相同向量的点积结果总是一个正数或零,因此不会出现负数的情况,也就不需要担心平方根计算失败的问题。

最后,我们还需要确保在代码中正确处理了位置更新和平方根运算的问题,虽然这些都是技术性的细节,但确保这些函数正常工作至关重要。

在这里插入图片描述

运行游戏,触发了断言错误

在实现过程中,发现如果在实体移动完成后尝试更改位置,可能会引发错误,特别是在发生断言失败时。问题的根源在于,函数并未考虑到将实体从一个有效的位置移动到无效的位置(例如将实体移动到零位置)。断言指出,传递的参数并非规范的,可能是由于试图将实体的位置更改为零位置,但程序没有处理这种情况。

在此情形下,断言实际上是在检查是否允许实体从一个有效位置移动到一个无效位置。解决方案是检查两个位置是否有效,并确保如果位置无效,则处理逻辑应单独进行处理,而不是直接进行位置更改。

该错误可能是由于没有为新的位置(例如零位置)做适当的验证,导致程序试图在无效位置进行操作。此时,必须确保断言逻辑可以正确捕捉这种情况,并在处理位置移动时做出相应的调整。

在这里插入图片描述

Assert导致断言错误的原因

在这里插入图片描述

在这个过程中,问题出现在实体的位置变更后,仍然可能会出现在错误的实体集合中。具体来说,在我们移动实体时,如果它仍然存在于“高频实体”集合(high set)中,它可能会被重复移动,这会引发问题。我们怀疑,实际上是实体被移出“高频实体”集合后,并没有及时地从集合中移除,因此它仍然处于集合中并被更新,导致其状态变得无效。

当改变实体位置时,应该首先检查该实体是否仍然属于“高频实体”集合。如果它已经不在正确的位置,应该从集合中删除,并确保不会继续被更新,直到下一次位置变化或其他条件被满足。特别是在移动实体出当前相机视野范围外时,它应该被及时移出“高频实体”集合。

然而,目前代码中没有处理这种情况的机制。当我们只更改低频位置(Low P)时,并没有执行从“高频实体”集合中移除实体的操作,导致实体仍然存在于集合中并可能会被重复处理。因此,问题的核心在于缺乏及时的移除操作,特别是在实体位置发生变化时。

为了解决这个问题,应该加入一段代码来处理从高频实体集合中移除无效实体。这样可以避免实体状态不一致,并确保其正确地更新或移除。

在这里插入图片描述

在这里插入图片描述

使用一种临时方法解决这个问题

问题在于当实体移出相机边界后,它是否应该被从高频集合中移除。虽然可以采用一些权宜之计,例如将实体的高频位置设置为某种极端的无效值,尽管这种做法可能勉强解决问题,但存在不优雅且容易出错的隐患。

当前问题的根源在于,当实体从低频位置中被移动到一个“不可存在的位置”后,没有任何机制能够将它从高频集合中移除。这导致了实体仍然留在高频集合中被更新,就好像它还在原来的位置。这种行为会带来潜在的逻辑错误。

为了解决此问题,需要一种更智能和更可靠的方式来处理这种情况,而不是依赖临时的技巧。尤其是,当涉及到两种频率更新机制(高频和低频)时,问题的复杂性加剧。如果基本逻辑没有处理好,这种问题可能会频繁出现,造成难以察觉的错误,不仅会影响开发效率,还会导致代码维护困难。

需要认真设计一种机制,确保在实体移出高频集合的边界后,能够正确地将其从高频集合中移除。这种机制必须易于理解和维护,以避免未来因类似问题而浪费时间。正确处理这一逻辑,对于确保更新机制的可靠性和优化开发流程至关重要。

在这里插入图片描述

尝试更合理地处理这个问题

我们对敌人系统的工作机制进行了一些回顾。当前的实现中,没有一个严格意义上的“敌人系统”,而是通过迭代高频实体并在每帧中更新它们的状态来运行。当更新某个实体(例如一把剑)时,在某些条件下,这把剑会被移出存在范围。

当实体被移出存在范围后,它不应再参与当前帧或后续帧的任何更新逻辑。因此,我们需要在更新循环中标记这些实体进行删除,或者在循环的一部分中将其移除。例如,可以引入一种控制流机制,跟踪需要移除的实体,并在循环结束后处理这些删除操作。

当前的一种可能的实现方法是在 SetCamera 调用中完成移除逻辑。这个函数会根据实体的当前位置和频率判断它们是否仍然处于活动范围内。如果可以扩展它来检查某些高频实体是否仍然有效,就能更智能地处理移除逻辑。例如,可以基于低频实体的有效位置来判断高频实体是否需要保留。

如果低频实体的位置无效(表示实体已经移出活动范围),我们就可以将该高频实体从高频集合中移除。这种处理方式相较于当前的实现更为合理,但可能需要额外的查询操作来确认低频实体的状态。如果这些额外的查询影响性能,可以稍后优化这一点。

在实现过程中还需注意,在移除操作执行之前,可能会存在一些边界情况。例如,即使实体已被标记为移除,但在当前循环结束之前,它可能仍然会被检查到。这种行为未必是错误,但需要关注可能由此引发的逻辑问题。

综上,我们需要为移出存在范围的实体设计一个更强健的移除逻辑,同时保证性能和代码的清晰性。这将有助于减少系统中可能产生的潜在错误,并提升开发效率。
在这里插入图片描述

运行游戏,发现剑的速度被设置为零的奇怪问题

在进行调整后,可以观察到问题确实得到了有效解决。然而,存在一个奇怪的现象,即剑仍然表现得非常缓慢。这种速度异常缓慢的行为令人疑惑,似乎与预期结果不符。

进一步观察发现,剑似乎没有获得任何速度增量,这种现象显得尤其奇怪。对于这种异常情况,尚不清楚原因所在,但这一点与系统行为的预期有明显偏差,需要进一步的分析和排查。

目前还无法确定是什么具体导致了这种情况,但这一点值得重点关注,以确保相关逻辑和速度更新机制的正常工作。

在这里插入图片描述

运行游戏,进行一些钩爪相关的操作

钩爪操作 是游戏技能发射一条绳索或钩

我们实际上正在处理一些问题。当前,我们发现一个快速解决方案被意外地保留在了代码中,尽管我们之前明确表示这只是一个临时补丁,但却没有将其删除。这种情况非常滑稽,同时也让人觉得无奈。

在编写代码的过程中,偶尔会犯一些错误,比如打错字,这在实时流中尤为常见。尽管也存在一些真实的代码错误,但修复这些错别字浪费了很多时间。为了能够一边讲话一边专注于代码,似乎需要找到更好的解决方法。

现在,我们正试图重新修复之前发现的一个错误,这个问题很可能完全是因为保留了那个临时补丁造成的。与此同时,我们注意到另一个细节:在某些情况下,代码并没有确保将位置重置到原来的状态。这种情况导致有时会意外触发一个类似“钩爪”的操作,这虽然看起来很酷,但并不是预期的结果。

接下来,我们将重点检查代码逻辑,确保位置在某些操作后能够正确还原,从而避免类似问题的再次发生。

解释这些钩爪操作是如何实现的

钩爪操作 是游戏技能发射一条绳索或钩

我们当前处理的问题涉及操作顺序的复杂性,这也是我们一直需要解决的事情。第一次发现这个问题是因为操作的顺序安排存在一定的不连贯性。具体来说,我们在处理输入数据时,没有确保某些操作的执行顺序。例如,一些与实体位置相关的操作,比如剑的位置更新,可能会在预期的时间点之前或之后发生。这导致了实体位置在高频空间和低频空间之间出现不同步的情况。

目前的代码中存在两个主要问题:

  1. 操作顺序的问题:在现有的实现中,某些操作被分散完成,这使得整体流程缺乏连贯性。例如,用户输入的处理与玩家位置的更新在不同的时机完成,这种分散的流程可能导致问题发生。
  2. 同步问题:当我们更改实体的位置时,如果该实体在高频空间中有一个表示,我们需要确保这些表示之间保持同步。而当前的系统中,这种同步容易被打破。

为了优化系统,我们需要解决这些问题:

  1. 统一输入与更新流程:收集用户输入后,以连贯的方式完成玩家更新,从而确保操作逻辑的一致性。
  2. 位置同步机制:设计一种机制,使得实体在高频空间和低频空间中的表示始终保持同步,避免因不同步引发的潜在错误。

此外,为了构建可扩展的系统,我们需要一个更加合理的设计。尽管当前的做法会导致代码更加复杂,但这样的复杂性有助于解决更大规模的实体更新问题。通过研究如何处理这些复杂情况,我们希望能够为处理大规模实体系统提供可行的解决方案。

接下来的任务包括:

  1. 提取当前代码中与位置相关的逻辑,并将其简化和优化,创建一个合理的定位系统。
  2. 基于现有的实际使用场景,进一步完善系统,使其在大规模使用时更加稳定和高效。

总结目标是通过精简和优化代码,提高系统在处理位置和实体移动方面的可靠性和可用性。

为什么你为每种向量类型创建了一个结构,而不是使用简单的浮点数组?

使用结构体而非普通的浮点数组来表示矢量类型,主要是为了能够重载数学运算符,使代码更加易读。通过重载运算符,例如*+,可以使表达式如a * b3 + c变得更加简洁和直观,而使用浮点数组时,代码会变得非常难以阅读。数组的方式需要使用临时变量进行多次运算,不仅复杂,而且可读性差。因此,使用结构体和重载运算符的方式,更加符合可读性和维护性的需求。

你对向量乘法运算符执行点积操作的看法是什么?

关于矢量乘法算子执行点积的做法,虽然它在着色器语言中常见(通常使用Hadamard积),但这种做法存在潜在问题,因为它容易让人误以为这是点积操作。建议将矢量乘法定义为Hadamard积,并且明确区分点积与Hadamard积,以避免错误。

为什么不将每种实体类型封装到各自的类中?

目前还没有做出具体的系统设计,所以还没有考虑将每种实体类型封装到独立的类中。即使倾向于面向对象编程和继承等概念,也不会在此时这样做,因为现在太早了,无法决定哪些内容应成为实体类,或者各个类应该如何设计。等到更多工作完成后,再决定如何拆分系统和设计类。

此外,对于像“剑”这样的实体有独立的类这一概念,认为这不是一个好的编程模型,也认为这种做法在游戏开发中效率不高。因此,在目前的开发阶段,不会采取这种方式。如果想深入讨论这个问题,最好等到稍后的阶段再进行。

XML 在这个项目中算合理选择吗?它不是库,但可能还不够 game?

关于XML文件,如果是自己写解析器并手动解析XML文件,这仍然符合当前游戏的标准,因此在这个项目中是可以接受的,只要不使用现成的库。

你不觉得 Low 实体已经包含了太多额外的内容,是时候分拆了吗?而且像剑这样的东西可能根本不需要低部分。

当前,低频实体可能已经包含了太多不相关的内容,因此有可能需要进行拆分。然而,目前没有足够的类型和复杂性支持这种拆分。计划在实体系统中实现更多的功能,特别是一些转移属性(例如,赋予物体特定的属性,如“蓝色火焰”)。例如,投掷剑可以获得“蓝色火焰”属性,击中怪物时造成相应的伤害。

在拆分实体系统之前,想先确保已经实现了这些功能,并确保实体系统中有足够的复杂性,否则拆分可能会基于错误的简化假设。因此,在目前的阶段,更希望继续将所有内容放在一个系统中,直到想到更多的类型和特性,再考虑是否进行拆分。此时并不需要担心内存占用,内存优化可以留到后续阶段进行。

我想到的不仅仅是内存占用,而是潜在的副作用,例如与属性传递相关的代码会引发定位等方面难以调试的问题。

当前的关注点不仅仅是内存占用,还包括潜在的不必要副作用。例如,处理转移属性的代码可能会引发难以调试的定位问题,或者其他一些问题。虽然现在调试确实较为困难,因为缺乏系统和调试工具,但这仍然是可以接受的,尤其是很多问题其实是打字错误,且当前很难完全解决。

目前,不值得投入时间去拆分低频实体,因为这项工作很可能会是无用功,尤其是当更多的内容尚未加入时,拆分的假设可能会是错误的。因此,暂时不进行这些工作是更好的选择,尽管这在某些情况下是一个艰难的决定。

仓库: https://gitee.com/mrxiao_com/2d_game


http://www.ppmy.cn/devtools/146293.html

相关文章

Three.js中调整相机视角的方式

在 Three.js 中,相机的视角(即相机的朝向)可以通过多种方式进行设置。以下是一些常用的方法: 1. 使用 lookAt 设置视角 camera.lookAt() 是 Three.js 中最常用的方法之一,用于设置相机看向特定的目标点。 示例&#…

FPGA上板项目(五)——UART测试,串口发送

目录 实验内容串口发送模块模块框图时序波形仿真结果 顶层模块设计时序波形仿真结果上板验证 实验内容 每隔1s,串口发送一次 “HELLO!” UART 相关的原理 野火FPGA跟练(四)——串口RS232、亚稳态、串口RS485 中做过阐述,本篇文章…

Rust 在前端基建中的使用

摘要 随着前端技术的不断发展,前端基础设施(前端基建)的建设已成为提升开发效率、保障产品质量的关键环节。然而,在应对复杂业务场景与高性能需求时,传统的前端技术栈逐渐暴露出诸多不足。近年来,Rust语言…

代码随想录38 322. 零钱兑换,279.完全平方数,本周小结动态规划,139.单词拆分,动态规划:关于多重背包,你该了解这些!背包问题总结篇。

1.零钱兑换 力扣题目链接(opens new window) 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 你可以认为每种硬币的数量是无限的。 示例 1&#xff1a…

云边端一体化架构

云边端一体化架构是一种将云计算、边缘计算和终端设备相结合的分布式计算模型。该架构旨在通过优化资源分配和数据处理流程,提供更高效、更低延迟的服务体验。 下面是对这个架构的简要说明: 01云计算(Cloud Computing) — 作为中心…

Docker【初识Docker】

目录 为什么会出现Docker这门技术喃? 应用开发和部署的困境 容器技术的先兆 Docker 的出现:简化容器化 Docker 技术的关键创新: Docker 的广泛应用和变革 什么是 Docker? Docker的历史 早期背景:容器化和虚拟化…

SQL—leetcode—175. 组合两个表

175. 组合两个表 表: Person -------------------- | 列名 | 类型 | -------------------- | PersonId | int | | FirstName | varchar | | LastName | varchar | -------------------- personId 是该表的主键(具有唯一值的列)。 该表包含一些人的 ID 和…

串口通信标准RS232、RS422、RS485有什么区别和不同

目录 第一个区别:硬件管脚接口定义不同: 第二个区别、工作方式不同 第三个区别、通信方式不同 第四个区别,逻辑特性不同 第五个区别、抗干扰性、传输距离和传输速率也不同 RS-232与RS-485对比 RS-422与RS-485对比 今天给大家分享的是&…