游戏引擎学习第136天
回顾
今天,我们的工作重点是继续探索之前搭建的资产系统,目的是最终定义我们的资产包文件格式。通过这个工作,我们希望能够创建一个符合我们要求的资产包文件。这样,我可以在直播之外的时间完成它,并为我们提供一个符合标准的新资产包。
资产系统的目标
我们正在继续开发我们的资产系统,这个系统的一个重要方面不仅仅是资产的流式加载,还涉及到资产分类的问题。我们希望通过这个资产系统,能够在不需要知道具体细节的情况下,灵活地使用各种资源。这样,游戏引擎就可以通过一种通用的方式请求所需的资产,而不需要关心具体的资产类型。比如,游戏引擎可能会请求一个英雄的精灵图,而不需要指定它具体是什么样子的,系统只需要返回一个最合适的精灵图。
这种方式非常有用,因为它允许我们在后期继续增加新的资产,而不需要修改游戏引擎中的任何代码。举个例子,如果我们已经设置好通过方向来请求英雄的精灵图,之后如果我们增加了更多的方向,只需要将新资源添加到资产系统中,游戏就会自动适应,而不需要开发人员再次修改代码。这大大简化了开发过程,并让游戏随着时间的推移越来越丰富,而不需要额外的开发工作。
此外,这种方式在开发过程中也非常有帮助。例如,如果某个艺术家还没有完成某个资产,但开发需要测试该部分功能时,资产系统可以返回一个类似的现有资产,这样开发人员就能继续工作,而不需要等待新的艺术资源。这种灵活性能够确保开发过程不被艺术资源的完成进度所拖延。
为了实现这一点,我们提出了一个资产标签系统。每个游戏资产都有一个主要类型,这个类型用于引擎识别资产的大类。比如,如果引擎请求一个“英雄”资产,它就会返回一个符合“英雄”类型的资源。然而,资产的具体细节,比如性别、大小、朝向等,可以通过不同的标签来定义。这些标签是由一对ID和对应的值组成的。例如,我们可以为“英雄”的性别定义一个标签ID(比如“性别”),然后用数字0代表男性,1代表女性,或者根据需求设置更复杂的分类。
通过这种标签系统,即便目前没有特定性别的英雄资源,资产系统也会返回最接近的匹配项。随着更多资产的加入,系统会自动适应并提供更丰富的资源。例如,如果我们开始只拥有男性英雄的资源,系统会返回一个男性的精灵图;当女性英雄的资源加入后,系统会自动选择女性英雄的资源。
除了性别这样的属性,我们还可以根据其他特征(如草的高矮)来分类和获取不同类型的资源。最开始时,如果我们只有一种草资源,系统会返回这一种草;当更多类型的草加入后,系统会根据预设的标签和分类,自动返回合适的草资源。
目前,我们已经成功地实现了这种简单的分类方法,使用了数组来管理不同类型的资产,并且可以动态地添加新的资产而不需要修改代码。接下来的任务是深入探讨资产系统的更多结构性方面,并尝试实现一些复杂的特性,比如英雄精灵图的多方向支持等。通过这些实践,我们希望能够完善资产系统,并让它在未来的开发过程中更加灵活和高效。
查看英雄位图
今天我们要讨论的是英雄精灵图的问题。当前我们有三部分构成一个英雄角色:头部、披风和躯干。接下来,我们要探索两种可能的方式来处理这些部分,但我不确定我们最终会选择哪种方式,也不确定我们会深入研究其中的哪一条路径。我会简单讨论这两种方法,让大家了解我在说什么。
第一种方式是让资产系统本身能够暴露关于英雄的信息。也就是说,游戏引擎并不需要知道一个英雄的具体组成部分,而是请求资产系统返回一个英雄,并告诉引擎这个英雄包含哪些部分,比如是否有头部、披风和躯干等。这种方法的好处是引擎不需要知道具体的组成内容,只需依赖资产系统返回的结果。
第二种方式是由游戏引擎来决定英雄的组成部分,然后请求资产系统返回这些具体的部分。在这种方法中,游戏引擎会清楚地知道每个英雄由哪些部分组成,并向资产系统请求这些部件。这两种方式各有优劣,带来的好处和权衡也是不同的。
经过思考,我认为我们游戏的性质可能更适合第二种方法。因为这是一个程序化生成的游戏,这意味着我们可能不希望资产系统来决定英雄的组成部分。相反,我们会随机生成英雄的组成部分,或者通过不同的解锁机制来确定英雄的构成。然后,这个由引擎决定的英雄构成会被传递给资产系统,要求它提供组成这个英雄所需的各个资产,而不是由资产系统来决定英雄应该由哪些部分构成。因此,我猜测我们最终会采用第二种方式,即由引擎决定英雄的部分,资产系统根据这些要求返回相应的资源。
总的来说,我们希望在系统中能够灵活地指定英雄的组成部分,这样每个英雄都可以根据程序化生成的规则拥有不同的外观和属性,而资产系统则提供这些部分的具体图像资源。
分类英雄部件
在资产系统中,每个部分(比如英雄的头部、披风、躯干等)都有一个选择,决定是将这些部分定义为资产类型,还是作为标签存在于英雄类型之下。我认为比较直接的方式是,如果引擎需要明确知道某个资产的具体内容,那么我们可以将这些部分定义为资产类型ID。
之所以这么做,是因为如果我请求一个英雄的头部,那么资产系统应该能够从资产池中返回符合要求的头部,比如男性头部、女性头部、瘦头或宽头等各种头部选择。而且返回的必须是“头部”这一类资产,而不能是其他类型的部分,例如不能把躯干当作头部返回,这样的替换是不合逻辑的。因此,这种情况下,应该使用资产类型ID而不是标签。
所以,我认为我们应该有像“资产头部”、“资产披风”、“资产躯干”这样的资产类型ID,这些是引擎需要明确了解的内容。接下来,如果我请求某个特定类型的头部、披风或躯干,系统就可以从资产库中返回这些类型的具体资源。
然而,我也开始思考,这些部件是否真的只属于英雄角色。比如,披风并不一定只能属于英雄,其他角色或物体也可能会有披风。因此,我们不一定需要过于详细地分类披风的种类,可能可以将其作为“披风”而不必关心它是否专属于英雄角色。
同样地,关于头部的定义,我也不确定是否需要做更多细分。或许我们可以直接使用“头部”这一概念,而不必特意区分“儿童头部”或“人类头部”。目前,我倾向于将其简化为“头部”这一资产类型。
总之,我们有可能会将这些不同的部分(如头部、披风、躯干等)定义为不同的资产类型,以便引擎可以在需要时准确请求并获得相应的资源。
分类面朝方向
另一个问题是关于英雄图像的处理,我们为四个不同的朝向分别准备了英雄图像,这就是为什么会有四个不同的图像。对于方向的处理,我认为它更适合作为一个标签ID来使用。因为方向是可以灵活的——你可以返回一个朝错方向的图像,作为一个暂时的替代品,这样也能满足需求。
此外,如果方向非常精确,我也不需要你返回一个完全精确地朝向,比如说精确到63度。你可以返回一个大致朝这个方向的图像,只要它足够接近就可以了。因此,我认为对于我们的资产标签系统,可以考虑将“朝向”作为一个标签来处理。
具体来说,我们可以为“朝向”设置一个标签ID,这个标签ID可以表示方向,比如可以是以弧度为单位的角度值,相对于正右方向的偏移角度,这样就能明确表示出朝向的方向。
接下来,我们可以尝试将英雄角色通过这种方式编码到资产系统中,而不再需要为英雄角色单独处理特殊情况,看看能否通过这种通用的方式来处理所有角色,而不需要对英雄做任何特殊处理。这样一来,整个系统将更加灵活,并且可以避免为每种特定类型的角色写额外的代码。
根据标签请求资源
在处理资产标签时,我们需要通过某种方式来传递所需的资产标签。初步计划是使用较大的向量(可能以后会改变),我将其称为“资产向量”。资产向量是一个包含所有可能的资产标签类型的实数值集合。通过创建一个这样的资产向量,能够指定需要的资产类型,并通过它来查找匹配的资产。
为了实现这一点,首先编写了一个类似于“pick best”(选择最佳)的函数,允许从现有的资产中选择一个最佳匹配的资产。在此基础上,我计划通过将匹配标签向量传递给资产系统来实现这一点。匹配标签向量中包含了所有期望的标签及其对应的权重,可以根据这些特征来筛选合适的资产。
具体的实现方式是,首先通过循环遍历所有资产,并检查每个资产的标签是否与给定的匹配标签向量最接近。通过计算实际标签和目标标签之间的差异,并根据权重向量进行加权,我们可以得出一个总的加权差异值。如果该差异值优于当前记录的最佳差异值,则更新最佳匹配的资产。
这种方法虽然能实现所需功能,但并不高效。遍历所有资产并计算差异可能会导致性能问题,特别是在资产数量很大的时候。虽然目前还不需要担心性能问题,但在未来进行性能分析时,我们可能需要考虑如何加速这个过程。
总的来说,这段代码实现了从资产池中选择最符合需求的资产,并通过匹配标签的方式来找到最佳资产。
使用BestMatchAsset获取英雄位图
首先,计划是完全移除当前的“英雄位图”系统,并用一种新的方式来指定和使用英雄的位图。具体来说,可以通过修改代码,使得不再直接使用“英雄位图”数组,而是通过一个新的“英雄位图ID”系统来获取相应的位图。
实现的步骤是,在原本存储英雄位图的地方,将其替换为一个“英雄位图ID”数组。每个数组元素代表一个与方向相关的位图ID,而这些ID可以通过调用新的资产系统函数来获取。这意味着可以根据英雄面向的方向动态地获取对应的位图ID,而不是直接从位图数组中读取。
为了确保新的函数可以正确工作,需要做一些简化处理,首先定义匹配向量和权重向量。匹配向量包含了每个资产的目标特性,权重向量则表示各个特性在匹配过程中应当给予的优先级。
接下来,通过调用“最佳匹配资产”的函数,可以根据这些匹配向量和权重向量来找到最合适的英雄资产。对于每一个资产类型(比如头部、披风、躯干等),需要分别为它们设置匹配向量,并将这些信息传递给新的函数。
在代码中,使用了一个简化的过程:暂时将所有权重设置为1,这样可以保证每个方向的匹配都有相同的优先级。同时,通过传递指针来确保正确引用资产ID。
目前,这段代码的目标是确保新的“英雄位图ID”系统能够正确替代旧的系统,尽管还没有完全设置好所有资产,但可以通过这种方式逐步实现。
加载英雄部件
现在需要设置资产表并查看它们如何工作。在这部分,我们首先要开始设置资产类型。通过使用 begin asset type
和 end asset type
来定义每种资产类型(例如披风),并将相应的资产(如披风位图)添加到资产列表中。
通过这种方式,每次请求披风时,系统都会自动返回正确方向的披风位图,因为我们在资产表中只添加了一个披风位图。无论指定什么标签,系统都会返回这个披风位图。这种方法同样适用于其他所有英雄的资产,例如头部、披风和躯干等。
这段代码的目的是构建资产列表,而不是游戏中实际运行时使用的代码。这部分代码仅用于构建资产包文件,不会在游戏运行时执行,因此在编写时并不需要关心实际的运行逻辑。
现在,如果指定了正确的资产类型,应该能得到一个面朝右的完整英雄图像。此外,系统还会自动获取熟悉的头部图像。然而,目前的问题是,顶部对齐的部分尚未完成,因此需要处理这些对齐的细节。
接下来,计划添加不同方向的头部资产。例如,头部应该能够朝不同方向(如上、下、前或后)旋转。这样可以使得角色的头部能够根据需要进行不同方向的显示。添加这些资产后,系统会根据优先级选择合适的资产,通常会选择第一个匹配的资产,因为其他的资产不会有更高的匹配优先级。
这些步骤将确保系统能够动态地选择和展示不同方向和类型的资产,尽管一些细节如对齐方式可能稍后再处理。
梳理一下之前push整个流程
按方向标记英雄部件
现在需要为这些资产添加一些标签,以便区分它们。首先,要为资产的朝向方向添加标签,例如 Tag
,用于标识当前资产的朝向。不过,目前的朝向方向仍然是整数,需要将它们对齐,以便正确区分不同的朝向。
在实现过程中,使用 AddTag
来为资产添加标签。AddTag
需要提供标签 ID,以表明正在添加的是什么类型的标签,然后需要提供一个值,该值将存储在标签表中。由于系统之前没有类似的功能,因此需要增加一个调试资产(DEBUGAsset)用于构建时存储最近创建的资产。
在 EndAssetType
处,调试资产会被重置为 0,以确保不会错误地影响下一个资产的构建。添加标签时,需要保证 DEBUGUsedTagCount
正确递增,以便在标签数组中分配新的标签,并确保当前的资产正确记录了所有附加的标签。
为了确保资产标签表能够正常工作,还需要增加断言(assert),用于检查资产索引不会超出总资产数量的范围。虽然这些断言不会影响最终的运行,但在调试过程中可以快速发现问题,避免浪费调试时间。
标签的存储方式类似于其他资产的存储方式,初始化时会设置一个空范围,每次添加标签时,会增加 DEBUGUsedTagCount
,同时调整资产的标签范围,以便正确存储新的标签。
目前标签数组还未分配空间,因此如果直接运行代码,会因为数组为空而导致断言失败。为了解决这个问题,需要在测试时预先分配一定数量的标签空间,具体数量暂时可以随意设定,而在实际加载时,会从磁盘中加载正确的数量,因此不会影响未来的实现。
完成这些设置后,运行程序时可以看到资产系统正确匹配了标签,从而不需要手动指定朝向方向,而是直接让资产系统返回相应的资产。目前,披风和躯干仍然返回相同的位图,因为尚未添加其他朝向的资产。只需在资产系统中添加不同方向的资产,系统就会自动返回正确的方向,不需要额外修改代码。
此外,由于英雄现在完全通过资产系统加载,因此系统还支持延迟加载等优化机制,使其在资源管理上更高效。当前的匹配方式运行良好,说明标签匹配功能已经正常工作。
重新审查FacingDirection
当前的面向方向处理方式似乎并不是最理想的,应该更加通用。目前的实现方式是通过 X 轴或 Y 轴的移动方向来编码朝向,但这种方式的灵活性较低,因此考虑修改它,使其更合理。
一种更优的方式是使用 atan2
函数来计算角色的朝向角度,并将其作为面向方向的值。这样一来,就可以在未来引入对角方向的精灵,而不只是限制在上下左右四个方向。此外,这种方法也能在角色动画方面提供更高的精度,使角色的动作更加流畅和自然。
不过,这种方法也会引入新的问题,需要在标签系统中进行一些支持。目前的标签系统并没有针对连续角度值的处理机制,因此需要考虑如何在系统中支持这一点。这个问题很有趣,值得深入探讨和优化,以确保系统能够适应未来更复杂的动画需求。
Blackboard:支持不同的比较函数
我们现在面临的一个问题是如何匹配位图与角色的朝向方向。目前,我们已经有了一些朝向的示例,例如 0 度、80 度、160 度等。理论上,当我们输入一个角度时,系统会匹配最接近的朝向,比如输入 10 度时,它会匹配 0 度的朝向,输入 75 度时,它会匹配 80 度的朝向,输入 125 度时,它会匹配 160 度的朝向,这样的匹配方式是符合直觉的。
但是,当我们输入 300 度的朝向时,问题就出现了。理想情况下,它应该匹配 0 度的朝向,因为这两个角度在 360 度的周期空间中实际上是相邻的。然而,由于当前的匹配方式是基于数值大小的线性比较,300 度更接近 160 度,而不是 0 度,因此会错误地匹配到 160 度的朝向,而不是 0 度的朝向。
这个问题的根本原因在于 角度是周期性的,而不是单调递增的数值。当角度超过 360 度或低于 0 度时,它会循环回到 0-360 度范围内。因此,我们不能直接使用普通的数值比较方法,而是需要一个特殊的匹配逻辑,使其能够正确地处理周期性数据。
为了解决这个问题,我们需要在比较两个角度时,先检查它们之间的差值是否大于 180 度。如果是,我们就需要进行调整,使其落入正确的匹配范围。例如:
- 如果角度差值超过 180 度,我们可以通过加上或减去 360 度的方式,使其映射到正确的范围。例如,如果 300 度需要与 0 度匹配,我们可以将 0 度视为 360 度,这样 300 度就比 360 度更接近,而不是比 160 度更接近。
- 另一种方法是对输入角度进行预处理,如果输入角度大于 180 度,就减去 360 度,使其变成负角度,以便更好地匹配正确的方向。例如,将 300 度转换成 -60 度,这样它就会更接近 0 度而不是 160 度。
这个问题说明,我们可能需要引入不同的匹配函数,以处理具有特殊性质的数据(如周期性角度)。目前,我们的匹配逻辑只是简单的数值比较,但对于类似“朝向”这样的数据,我们必须定义特殊的比较方法,以确保匹配结果是正确的。
我们不知道未来会有多少类似的问题需要解决,但至少朝向方向的匹配是一个明确需要处理的情况。接下来,我们可以尝试实现这个新的匹配方法,看看它是否能够正确匹配周期性的角度数据。
使用实际的朝向角度
我们接下来的目标是修改 朝向方向(facing direction) 的计算方式,使其基于 角度 而不是简单的数值比较。为了实现这一点,我们计划使用 atan2 函数,它能够计算出实体的 运动方向角。
修改思路
-
使用 atan2 计算角度
- 由于
atan2(y, x)
可以根据给定的 x 和 y 分量 计算出角度,因此我们可以利用它来获取实体的朝向角。 - 具体实现方式是:
其中Entity->FacingDirection = ATan2(Entity->dP.y, Entity->dP.x);
dP.x
和dP.y
是 实体的速度或加速度分量。
- 由于
-
将 facingDirection 变量修改为浮点数
- 之前的
facingDirection
可能是一个枚举或整数索引,现在我们需要将其更改为 浮点数,用于存储角度值。
- 之前的
-
在资源(asset)系统中定义角度映射
- 之前的朝向可能是基于 固定的索引(比如 0 = 右,1 = 后,2 = 左,3 = 前),现在我们需要改为基于 角度的映射:
angleRight = 0.0f * Pi32; angleBack = 1.0f * Pi32; angleLeft = 2.0f * Pi32; angleFront = 1.5f * Pi32;
- 这里,我们使用 弧度制(radians)来表示角度,具体的角度值如下:
0.0f * Pi32
(0 度):面向右1.0f * Pi32
(180 度):面向后1.5f * Pi32
(270 度):面向左0.5f * Pi32
(90 度):面向前
- 之前的朝向可能是基于 固定的索引(比如 0 = 右,1 = 后,2 = 左,3 = 前),现在我们需要改为基于 角度的映射:
-
考虑是否使用 τ(Tau)代替 π(Pi)
- 在定义角度时,我们通常使用 π(Pi),但有些开发者更喜欢使用 τ(Tau)= 2π,因为它表示完整的 360 度。
- 目前,我们的代码使用的是
Pi32
,但可以根据需求改为 τ 记法,比如:angleBack = 0.5f * Tau32; // 180度
- 需要检查 标准库 或 自定义定义 中是否包含
Tau32
,如果没有,可能仍然需要使用Pi32
。
预期效果
- 这样一来,实体的朝向将 基于角度计算,而不是依赖于固定的索引值。
- 资源系统能够自动匹配最接近的朝向角,从而更好地支持 角度插值 和 动态朝向调整。
- 未来如果想要支持 更多角度(如 8 方向甚至 360 方向),只需增加对应的角度资源即可,无需额外的改动。
后续计划
- 测试修改后的匹配系统,检查是否能够正确匹配最近的角度。
- 观察是否需要进一步优化角度匹配的逻辑,例如是否需要对 边界情况(接近 360° 或 0°) 进行特殊处理。
- 评估是否有必要在 资源系统中添加额外的元数据,以便更好地支持 动画 和 过渡效果。
现在我们可以继续实现这些改动,看看效果如何。
调试破损的方向
左右不行上下可以修改去掉绝对值
在当前的朝向计算中,我们发现向下(down)方向的匹配存在问题,并且向左(left)方向也没有正常工作。这导致角色在某些方向上的朝向错误,具体原因需要进一步调查。
问题分析
-
首先检查实体的速度(velocity)数据
- 由于
atan2(y, x)
依赖于速度分量dP.x
和dP.y
计算角度,因此需要确认当前的速度数据是否正确。 - 通过调试,我们看到:
- X 分量(dP.x)接近 0
- Y 分量(dP.y)为 -0.6
- 这意味着实体正在向下移动,按理说计算出的角度应该接近 270°(-90°)。
- 由于
-
检查
atan2(y, x)
计算出的角度值- 计算得到的值是 -1.57(即 -π/2)。
- 这与预期的 270°(3π/2)不同,原因是
atan2(y, x)
返回的角度范围是-π
到π
(即 -180° 到 180°):- 向下运动的角度 本应该是 270°(3π/2),但
atan2
给出的是 -90°(-π/2)。 - 这样在匹配朝向时,会导致误匹配问题。
- 向下运动的角度 本应该是 270°(3π/2),但
-
确认这实际上是我们之前讨论的周期匹配问题
- 由于角度是周期性的(0° 和 360° 是相同方向),但
atan2(y, x)
只返回-180° 到 180°
,这导致:- 本该是 270° 的方向变成了 -90°
- 在匹配最近朝向时,-90° 可能会错误匹配到 0° 而不是 270°
- 这正是之前提到的周期性角度匹配错误。
- 由于角度是周期性的(0° 和 360° 是相同方向),但
修复方案
为了修复 atan2(y, x)
返回角度的问题,我们需要:
-
将
atan2(y, x)
的返回值转换为0 - 360°
角度范围- 解决方法是:
entity.facingDirection = atan2(dP.y, dP.x); if (entity.facingDirection < 0) { entity.facingDirection += 2.0f * Pi32; // 将负角度转换为 0-360° 范围 }
- 这样:
-π/2
(-90°)变成3π/2
(270°)-π
(-180°)变成π
(180°)- 所有角度都映射到
0 - 2π
(0 - 360°)的范围。
- 解决方法是:
-
确保资产(asset)系统匹配正确的角度
- 由于
atan2(y, x)
计算出的角度范围改变了,我们需要确保朝向匹配逻辑仍然正确。 - 资源系统的角度定义保持不变:
angleRight = 0.0f; angleBack = Pi32; angleLeft = 1.5f * Pi32; angleFront = 0.5f * Pi32;
- 但匹配逻辑可能需要调整,以适应新的 0-360° 角度范围。
- 由于
预期修复效果
- 所有角度转换为
0-360°
范围后,向下方向应该正确匹配到270°
。 - 向左方向也应该正确匹配到
180°
。 - 修复角度周期性问题,避免 -90° 被误匹配到 0°。
- 提高角度匹配的准确性,防止其他方向的误匹配问题。
接下来,我们可以继续测试,看看是否还存在其他匹配错误。
临时修复:移动领域
我们目前在修正角色朝向(facing direction)的计算方式,遇到了一些问题,并且正在进行修正。
问题分析
我们之前使用 atan2(y, x)
来计算角色的朝向角度,并将其存储为浮点数。但 atan2(y, x)
返回的范围是 (-π, π]
,也就是 (-180°, 180°]
,这导致了一些匹配上的错误。例如:
- 向下移动的角色可能会错误地匹配到
180°
而不是0°
,因为atan2
可能返回-π/2
而不是期望的270°
。 - 角色在
0°
和360°
的边界时,由于方向匹配不支持周期性,会出现错误。
解决方案
为了正确匹配角度并处理周期性问题,我们做了以下调整:
-
修正
atan2(y, x)
角度范围- 由于
atan2
可能返回负值,我们在计算后如果角度小于0
,则加2π
让角度回到[0, 2π]
(即[0°, 360°]
)。
if (entityFacingDirection < 0) { entityFacingDirection += 2.0f * Pi32; }
这样可以确保所有角度都处于
0°
到360°
之间,避免负角度带来的匹配错误。 - 由于
-
匹配系统的周期性问题
- 之前的匹配逻辑没有考虑到
0°
和360°
本质上是同一个方向。 - 现在,我们在匹配时检查两个角度的差值是否超过
180°
,如果超过,则调整它们以确保正确匹配。
- 之前的匹配逻辑没有考虑到
当前结果
修正后,我们发现:
- 角色的朝向匹配更加稳定,不会再因为
atan2
产生负值导致匹配错误。 - 在
0°
和360°
交界处的方向切换变得更加平滑,不会再出现错误匹配的情况。 - 仍然需要处理一些细节,比如低速运动时的方向匹配问题。
目前来看,这个修复方向是正确的,但仍需要进一步测试和优化,以确保匹配逻辑在各种情况下都能正常工作。
为什么要加上 2 π 2\pi 2π?
atan2(y, x)
返回的角度范围是:
(
−
π
,
π
]
(-\pi, \pi]
(−π,π]
也就是说:
- 右侧 x > 0 x > 0 x>0 时,角度在 ( − π 2 , π 2 ) (-\frac{\pi}{2}, \frac{\pi}{2}) (−2π,2π) 之间。
- 左侧 x < 0 x < 0 x<0 时,角度在 ( − π , − π 2 ) (-\pi, -\frac{\pi}{2}) (−π,−2π) 或 ( π 2 , π ] (\frac{\pi}{2}, \pi] (2π,π] 之间。
- 正上方 ( y > 0 , x = 0 ) (y > 0, x = 0) (y>0,x=0) 时,角度是 π 2 \frac{\pi}{2} 2π。
- 正下方 ( y < 0 , x = 0 ) (y < 0, x = 0) (y<0,x=0) 时,角度是 − π 2 -\frac{\pi}{2} −2π。
但是,我们希望角度范围是:
[
0
,
2
π
)
[0, 2\pi)
[0,2π)
这样所有的角度都是正值,方便在 0° 到 360° 之间进行匹配和比较。
数学推导
如果 atan2(y, x)
返回的是负角度(即
−
π
<
θ
<
0
-\pi < \theta < 0
−π<θ<0),那么我们可以通过加上
2
π
2\pi
2π 来转换到正值范围:
θ ′ = θ + 2 π , 当 θ < 0 时 \theta' = \theta + 2\pi, \quad \text{当} \theta < 0 \text{ 时} θ′=θ+2π,当θ<0 时
举个例子:
atan2(-1, 0)
返回 − π 2 -\frac{\pi}{2} −2π(即 -90°)。- 如果加上 2 π 2\pi 2π,就变成了 3 π 2 \frac{3\pi}{2} 23π(即 270°)。
atan2(-1, -1)
返回 − 3 π 4 -\frac{3\pi}{4} −43π(即 -135°)。- 如果加上 2 π 2\pi 2π,就变成了 5 π 4 \frac{5\pi}{4} 45π(即 225°)。
这样所有角度都会落在 [ 0 , 2 π ] [0, 2\pi] [0,2π] 之间,方便用于匹配。
总结
我们使用 atan2(y, x)
计算角度后,如果它是负数,就加上
2
π
2\pi
2π 来让它保持在 [0, 2π]
之间,以便和 0° ~ 360° 的范围匹配,这样可以避免匹配错误。
明天的任务:修正周期处理
我们今天基本完成了想要做的事情,接下来将会讨论如何处理变量类型,具体是如何添加标签类型。明天的目标就是开始处理这个部分,加入标签类型,并修复之前的 bug,确保这个 bug 显示得更加明显,然后再修复它。这个工作量应该不大,但因为涉及到一个新的系统,所以不想现在就开始,明天再继续处理。
总体来说,今天的任务就差不多完成了,接下来我们会继续按照计划推进,明天重点是实现标签类型的添加,并解决相关问题。
所有标签匹配是否可以周期性地进行,然后当我们不想它回绕时,只需将周期设得非常大?
讨论中提到一个有趣的想法:是否可以将所有标签匹配都设置为周期性的,并通过设置非常大的周期来避免它们的值被包裹(wrap)。这个想法听起来不错,需要进一步思考。明天在实施时可以考虑这个方案,可能可以通过统一处理所有标签,然后通过一些参数来调整周期性,这样通过快速的数学函数就能获得很多可选的功能选项。这个思路值得进一步探讨。
英雄有单独的位图用于他的精灵。这会是游戏中其他种类精灵的反复出现的事情吗?
游戏中的英雄角色拥有多个不同的精灵图块(bitmaps),这种做法在游戏中的其他角色精灵上也会是常见的。许多精灵将会有不同的部分,以便进行动画效果的展示,比如角色的动作、变化等。比如,玩家可以获得不同的披风,这些披风可能有不同的属性或者特殊效果,像魔法披风之类的。因此,游戏需要提供一种方式,使得玩家可以自由地更换这些装备,确保可以对绘制的内容进行更细粒度的控制。这种设计提供了更大的灵活性和可定制性,适应不同的游戏元素。
从1到10的比例,你认为Windows API有多傻?
对于Windows API的评价无法简单地用一个数字来表示,因为它的设计质量因不同的API而异。有些Windows内核API设计得非常好,而一些像Windows事件跟踪这样的API设计则非常糟糕。Windows API是由很多不同的人在多年的时间里编写的,有些人非常擅长编程,他们编写的API质量较高,而另一些人则可能没有经验,导致他们编写的API质量较差。所以,Windows API的质量是参差不齐的,根据具体的API,不同的部分可能会有不同的评价。
要获得你想要的角度,难道你不能只将角度除以45,得到一个可以切换的数字吗?你甚至可以进一步将这个数字对想要的角度数量取模
之前的想法是将角度除以45,得到一个数值然后进行切换,甚至可以进一步通过取模运算来得到所需的角度数量。但现在不再采用这种方法了。新的想法是让引擎直接请求所需的角度,然后根据当前的艺术资源包返回最接近的匹配。这样,如果以后添加更多的艺术精灵,游戏的表现会更平滑,而不需要提前限定角度数量。这种方式更加灵活,可以随着资源的增加而自动适应,避免了之前的方法中的限制。
Tau显然是更优的常数。请删除代码中的每一个Pi实例!
为了提升精度并避免使用π,决定使用τ(tau)。τ值为2π,具有更好的可读性,尤其是在处理360度和180度等角度时,能够提供更简洁的代码。
在实现过程中,首先搜索了τ的值,并确保其精度足够。然后,替换掉了代码中的所有π实例,用τ代替。这样,代码中关于角度的计算变得更加清晰,特别是在需要处理一圈(360度)的情况时,τ的使用能让角度计算变得更加直观和简洁。
此外,在替换过程中,检查了所有相关的代码,确保在合适的地方替换为τ,并且保证替换后的代码逻辑没有问题,精度也得到了保持。这样做能够提高代码的可读性,同时避免了潜在的精度丢失问题。
https://tauday.com/tau-digits
τ(Tau)是数学中的一个常数,表示圆周率的一圈,即圆的周长与半径的比值。它的值等于 2 π 2\pi 2π(约等于 6.28318)。在很多领域,特别是数学和物理中,τ被提议作为替代π的常数,因为它在处理角度和周期性问题时更加直观。
具体来说,τ的应用包括:
-
角度计算:
在角度的表示上,π常用于描述半圈(180度),而τ表示完整的一圈(360度)。因此,使用τ可以让圆的度量更加简洁。例如,360度等于τ,而180度则等于τ的一半( τ 2 \frac{\tau}{2} 2τ)。 -
波动和周期性现象:
在涉及周期性运动的数学中,τ可以让周期性的计算更加直观。例如,正弦波和余弦波的周期常常以τ为单位,使得波动的描述更加清晰。 -
提高代码可读性:
在程序设计中,如果使用τ来代替2π,代码中处理周期性问题时的常数会更加明确,避免了使用两倍π的冗长。
综上,τ作为常数提供了一种比π更自然的方式来描述完整的圆和周期性,尤其在与角度、周期相关的计算中,τ的使用能让表达更加简洁和直观。
你怎么看那些用空格而不是制表符的人?
关于使用空格而不是制表符(Tab)的问题,有人认为使用空格更好。空格被认为是一种更为一致和直观的做法,而制表符在很多情况下被认为是一个不完全实现的功能。通常,当试图使用一个半实现的功能时,往往会遇到各种问题。
具体来说,空格的优点在于它的统一性,代码的可移植性更强,因为无论在哪个编辑器或环境中,空格的显示效果都是一致的。而制表符则存在一个问题:不同的编辑器和环境对制表符的宽度可能有不同的默认设置,可能导致代码在不同的环境下显示不一致,增加了开发的复杂性。
所以,空格被认为是一种更加稳定、清晰的选择,尤其是在团队协作和代码共享的情况下,避免了因制表符宽度差异而导致的潜在问题。
为什么你用角度的反正切来确定资源?
使用切线(tangent)来确定资产的原因是因为需要根据实体的加速度或速度方向来计算其面向的角度。通过使用 atan2
函数,可以根据物体的水平和垂直速度分量来计算出面向的角度。这种方法能够处理角度的周期性问题,即角度值不会超出[-π, π]的范围,而是会通过正负180度来实现对称,从而避免出现角度值超过360度或-360度的情况。
这种方法的好处是能够精确地反映出物体的朝向,并且通过计算得到的角度值可以直接对应到游戏中的资产图形。因为游戏中的资源往往是根据角度来组织和匹配的,通过这种计算方式,可以确保当物体的角度发生变化时,能够准确地匹配到对应的图像或资源,从而实现平滑的动画效果。
Blackboard:查找艺术资源
在这种情况下,目标是将一个方向向量编码为一个浮动值(即角度),以便用于查找对应的资产。使用单一的浮动值可以简化处理,因为只关心向量的方向,而不需要关心其大小(即向量的幅度)。
为了将向量转换为角度,使用了反正切函数(atan
)。反正切函数的公式是:如果已知一个向量的x分量和y分量,则可以通过反正切函数(atan
)求出该向量的角度。其基本原理是,反正切函数可以求解一个已知x和y分量的角度,这个角度与x轴的夹角有关。
在理想情况下,使用简单的atan(y/x)
可能能得到正确的角度,但是问题是,atan
函数只会返回一个角度,并且没有考虑到向量在四个象限中的不同。因为如果只通过y/x的比值来计算,正负的情况会消失,导致无法区分在不同象限中的向量。例如,正x负y的向量和负x正y的向量,其x和y比值相同,但是它们的实际角度是不同的。
为了解决这个问题,使用了atan2
函数。与atan
不同,atan2
会同时考虑x和y的符号,从而正确地返回对应的角度。这意味着atan2
不仅能给出向量与x轴的夹角,还能区分向量所在的象限,使得计算更加准确。atan2
的作用是提供一个更精确的角度,不仅仅关注偏离x轴的角度大小,还能够根据向量的实际方向给出正确的角度值。
因此,使用atan2
能够处理这种情况,使得计算得到的角度更加符合实际需求,避免了象限错误的问题。
使用几个空格:2、3还是4?
对于代码缩进的空格数量,每个人可以根据自己的喜好选择使用两、三个或四个空格。在这个案例中,使用了四个空格进行缩进。关键在于保持一致性,不论使用多少空格,最好在整个项目中统一风格。
至于实现资源加载的优先级,通常会根据不同资源的类型、使用频率或者其它特性来设定加载的优先级。例如,核心资源可能会设置较高的优先级,确保它们首先被加载,而一些次要资源则可以在系统空闲时加载。
你会实现资源加载优先级吗?比如,玩家应该先加载,这样一开始就不会是看不见的
例如,玩家角色应该优先加载,以确保在游戏开始时不会是不可见的。虽然可能不会明确实施资源加载优先级,但会使用资源预加载的方式。预加载意味着在实际需要这些资源之前,提前开始加载它们。这样一来,等到需要使用时,资源就已经加载完成,不会出现缺失或加载延迟的问题。
使用矩阵来进行旋转会不会是一个好主意?这样往后做变换和缩放就会更容易
关于是否使用矩阵来进行旋转以便将来更容易实现缩放和变换,当前的做法其实已经在使用类似矩阵的方式,只是没有明确称之为矩阵。我们通过x轴和y轴来表示,并且可以对它们进行缩放,从而实现旋转。并没有直接使用角度进行旋转。
如果是指资产查找系统的编码,那么我认为不应该使用矩阵。因为在查找方向时,矩阵会增加更多的复杂度,需要匹配四个标签而不是一个标签,这样就失去了简洁性,尤其是在旋转和缩放应当分开匹配的情况下。旋转和缩放是两个独立的量,应该分别处理,而不是将它们混合在一起。这样可以确保在方向和大小上有更清晰的区分。
也许我错过了,但你在用cmath的atan2吗?
目前我们没有替换C运行时库的数学函数,关于这一点还不确定具体会怎么做。基本上,我们计划为atan2
实现一个小型数学模块,使用SIMD技术来加速计算。具体来说,它可能会支持一次计算四个值(四宽度的atan2
),虽然不确定是否真的会用到这个优化,但我们会尝试让atan2
能够同时处理多个值,以提升效率。