仓库:
https://gitee.com/mrxiao_com/2d_game
(仓库满了) gitee 好像一个仓库最多1G
https://gitee.com/mrxiao_com/2d_game_2
后面改到https://gitee.com/mrxiao_com/2d_game_2 仓库
代码占的内存不大主要是markdown截图700多兆比较占内存
Blackboard: 以对处理实体对的方式进行处理
先简单回顾一下昨天我们发现的一些有趣的东西,关于实体如何处理碰撞。
之前,我们实现了一些简单的碰撞处理。我稍微想了一下,如何更好地处理这些事情。我们最终实现的是一个系统,假设你有一个实体,比如说英雄和怪物,或者你投掷了一把剑,这把剑可能想要穿过怪物并造成伤害,比如穿刺效果之类的。
我们有一些方法可以考虑如何处理这个碰撞情况,尽管看起来很简单,但实际上要做得好,涉及到很多细节。
之前我实现的方法是,对于任何实体对(比如剑和怪物),当剑碰到怪物时,我们会有一个系统来记录这个碰撞,并通过一个小表格来跟踪它。
具体来说,当剑碰到怪物时,我们会把"剑-怪物"这一对记录在表格中。从那时起,每一帧都会检查一下,看这对是否已经处理过了,如果已经处理过,就不再处理。
当剑完成它的运动,达到最大距离并需要移除时,我们会清除这个表格中所有与这把剑相关的条目。
这个方法是一个简单且稳健的方式,用于处理像投射物的碰撞,能够帮助跟踪已经处理过的碰撞,并且能够记录任何需要的细节,因为这个表格可以存储任何我们想要的信息,并且在整个碰撞的生命周期中保持有效。
Blackboard: 使用该表格处理楼梯重叠
我们昨天讨论的目标是实现一个楼梯系统,比如说角色走下楼梯进入下一层。想象一下角色在楼梯的顶部,正在沿着楼梯向下走。我们希望实现的效果是,当角色与楼梯重叠时,他们的 z 轴值应该向下变化,这样就能让角色"进入"下一层。
具体来说,当角色在楼梯的顶部时,z 值应该在角色与楼梯重叠时变为下一个楼层的高度,而当角色到达楼梯底部时,z 值应该恢复到当前楼层的高度。
为了简化这一过程,我们希望有一个系统可以让我们轻松实现这一点,而不需要做过多的手动处理。这种系统应当能够让我们通过简单的代码表达这种行为,而不必进行复杂的计算。
我在思考这个问题时意识到,我们完全可以利用现有的碰撞表格来处理这一需求。具体来说,在每一帧开始时,我们可以检测角色是否与楼梯重叠,如果重叠,就将相关信息加入到表格中,直到这一帧结束时,角色不再重叠时,从表格中移除相关信息。
通过这种方式,我们可以统一处理所有的碰撞和重叠情况,而不需要为楼梯这样的特殊情况编写额外的处理逻辑。只需一个简单的表格来记录角色与不同物体的交互,代码就能变得更加简洁和清晰。
这看起来是一个非常合理的做法,能够让代码更加统一和易于理解,所以今天的目标是进一步探索这个思路。
Blackboard: 在楼梯内移动
另一个需要考虑的问题是,当角色在楼梯区域内移动时,我们希望根据角色的位置调整其 z 轴值。因为楼梯有点像斜坡,我们可能需要对其进行一些特殊处理,以确保角色的移动过程看起来合理。
具体来说,我们可以在角色移动时,在进行常规的区域检查后,再检查目标位置是否仍然位于楼梯区域内。如果目标位置仍在楼梯区域内,我们就将角色的 z 轴位置调整到适当的楼梯高度。这样,角色就会顺畅地沿着楼梯移动。
不过,这种方法可能涉及较为复杂的三维处理,可能会超出当前需求,甚至不一定是我们想要的处理方式。虽然从现实模拟的角度来看,这样做能够更准确地反映楼梯的物理特性,例如将 z 值与每个楼梯台阶对齐,但我们也需要考虑是否真的需要这么精细的处理。
总之,虽然可以有更多细节上的改进,例如根据实际楼梯位置精确调整角色的 z 值,但是否需要走这条路,仍然需要权衡。
Blackboard: 让楼梯控制移动
最后,考虑到简化处理的可能性,我想到了一种更简单的方法。我们可以选择不在每一帧的开始和结束进行处理,而是将楼梯的处理作为一个单独的步骤。也就是说,在每一帧的开始,如果角色已经进入了楼梯区域,我们就把这次移动当作楼梯的移动来处理。在此情况下,楼梯会对角色进行必要的操作,然后就不再继续检查,直到角色在下一帧移出楼梯区域。
这种方法的好处是,它将楼梯的处理限制为每次进入时只进行一次检查,而不是在每一帧中不断进行。这样,我们避免了复杂的重复计算,同时也简化了代码的实现。
这是一种可能的简化方式,虽然可能没有之前提到的更精细的处理方法那么逼真,但它提供了一种实现的捷径,值得考虑。
Blackboard: 熔岩
假设我们考虑像毒地或熔岩这样的特殊地形,处理这些区域时我们需要考虑角色进入和离开这些区域的情况。比如,当角色进入熔岩时,我们需要处理他们受到伤害的逻辑,可能是基于时间或者基于距离来计算受伤的时间。为了实现这一点,我们可以采用点采样的方法来进行处理。
点采样的方法相对简单,基本的思想是在每一帧开始时判断角色是否处于熔岩区域。如果角色处于熔岩区域,就计算相应的伤害;如果不在,则不计算伤害。这样我们就可以简单地做一个包含性检查,不需要在每一帧都处理碰撞事件或其他复杂逻辑。
对于是否要做得更精细,可以在后续逐步优化。如果当前只是简单的检查进入和离开熔岩区域,处理时传递整个帧的时间差(dt)来计算伤害,这样做可以简化实现,避免对每一帧的时间进行更精细的拆分。可以先做简单的检查,并将其作为一个待办项,未来可能会根据需求引入更智能的处理方式,比如使用表驱动的方法来处理更复杂的情况。
当前的做法是先使用简单的单独检查,等到游戏机制发展到一定程度时,再决定是否改进为更复杂的处理逻辑。
game_sim_region.cpp: 移除 WasOverlapping 检查
在处理特殊区域(如楼梯或熔岩)时,可以简化之前的处理逻辑,避免更新重叠实体列表。可以选择直接通过检查初步的包含关系来处理这些区域,而不再进行复杂的重叠处理。具体来说,不再需要维护"是否重叠"的标志,也不需要通过重叠实体列表来判断交互情况。通过简化这些步骤,楼梯等区域就不再是基于碰撞的对象,而只是需要在初始包含时应用一些特殊规则。
这种方法的好处是避免了不必要的复杂性,允许直接在需要的时候应用规则,简化了代码的处理流程。接下来可以清理掉不再需要的部分代码,使逻辑更加清晰和高效。
添加 HandleOverlap 到 MoveEntity
目前的做法是,将"处理重叠事件"的逻辑提取为独立的函数,命名为handleOverlap
。这个函数会在每帧的最开始执行,用于检查任何可能重叠的区域。对于所有发生重叠的实体,将会根据预定的规则进行处理,而不再记录每一个重叠事件的状态。
具体而言,移动的实体(如玩家)会先检查是否与其他区域(如楼梯或熔岩)重叠,然后在handleOverlap
函数中处理相关规则。这样,重叠处理逻辑会在每帧的初始阶段执行,而不再依赖于后续的碰撞步骤。
此外,命名和结构也进行了优化,使代码逻辑更加清晰。
编译并仔细检查代码是否仍然有效
需要确认一下代码是否仍然正常运行,确保没有任何问题,并验证修改后代码的正确性。
编写 CanOverlap 和 HandleOverlap
现在需要编写一个函数来处理实体的重叠事件,确保如果英雄或其他对象与楼梯区域重叠时,可以根据楼梯的位置将其向上或向下移动。虽然渲染部分目前还没做,但这是接下来的任务之一。
具体来说,处理重叠时,需要检查实体所在的区域类型,当前的主要处理是楼梯区域的情况,未来可能会增加类似岩浆等其他区域的处理。如果区域类型是楼梯区域,就进行处理,调整实体的位置和速度,使其能够沿楼梯移动。
时间步长(dt
)也会作为一个参数传入,以便在未来可能需要根据时间来调整效果时能更准确地处理。
考虑重新构思 Ground
目前的思路是,将重力加到加速度中,并通过在楼梯井区域上进行处理来修改地面位置。虽然这种方法听起来有些奇怪,但它可以提供一个地面的位置概念。由于现在缺乏明确的地面概念,可能需要在后续逐步建立一个清晰的地面语义,直到它能够在整个系统中合理运作。
具体而言,可以在碰撞处理过程中,通过HandleOverlap
函数修改地面位置。虽然这看起来有些不太符合常规,但它是基于当前缺乏强地面概念的前提下的解决方案。对于地面而言,游戏中的地面应该是固定的,只有在特殊情况下,如楼梯井等区域,才需要进行额外处理。
因此,计划在代码中传递地面的地址以便后续修改,未来可能会将相关的所有信息打包传递给HandleOverlap
函数进行处理。目前,考虑到处理的简便性,地面并不会被建模为复杂的形状,而是通过简单的碰撞体来处理。虽然这个方案目前并不完美,但还是会继续尝试,期望能找到更好的处理方法。
继续编写 HandleOverlap
在处理楼梯井时,将传递地面高度,以便根据角色在楼梯井中的位置调整地面高度。此外,意识到绘制楼梯井的方式不应该使用之前的岩石图形,而应该使用矩形。接下来,考虑在图形中使用矩形来绘制楼梯井,并在相关的函数中调用矩形绘制函数。
同时,提到颜色处理功能已经被实现,尽管之前有人提到将来会加入颜色处理,但实际上已经在代码中加入了颜色的处理。
game.cpp: 将楼梯绘制为矩形
将楼梯绘制为矩形,而不是使用奇怪的尺寸推测,这样可以直接使用实际的实体维度,方便预算和调整。使用PushRect
来绘制,确保位置是相对于相机的偏移,而尺寸则直接使用实体的实际尺寸。颜色选择为黄色,这样可以清晰显示楼梯,最终效果符合预期。
查看楼梯并描述我们想要做的事
要实现能够在楼梯上移动,并随着移动进入不同的层级,这个过程相对简单。但实际操作时会变得稍微复杂,因为除了基本的楼梯移动外,还需要处理其他情况,决定如何处理这些额外的复杂性。
Blackboard: 处理楼梯的两端
最后需要处理的情况是,当玩家走到楼梯上时,楼梯有两个端口。如果玩家走到楼梯的一端,他们应该顺利下楼;如果走到另一端,则需要防止他们穿过楼梯或直接降到另一层。因此,需要考虑如何阻止玩家从错误的端口进入楼梯。可以通过添加阻挡物来防止这种情况,或者在楼梯的处理逻辑中直接处理。最终会选择哪种方法,还需要根据实际情况来决定。
Blackboard: 矩形映射的概念
为了解决坐标变换的问题,首先要考虑的是将三维矩形的最小角点设置为原点(0, 0, 0)。这样,所有其他点相对于这个最小角点的位置会被重新映射到一个标准化的坐标系内,坐标范围从0到1。这意味着每个坐标值都会被归一化,以便能够表达其在矩形内的位置。例如,如果一个点在X轴的坐标是矩形宽度的40%,那么它的归一化X值就是0.4。
具体操作上,首先需要将每个点减去矩形的最小角点坐标,然后将结果除以矩形在每个轴上的长度,从而得到归一化后的坐标。这样,无论矩形的大小如何,所有点的坐标都会被统一到0到1的范围内,这样方便进行后续的处理和比较。
假设有一个三维矩形,其最小角点坐标为 ( x min , y min , z min ) (x_{\text{min}}, y_{\text{min}}, z_{\text{min}}) (xmin,ymin,zmin),最大角点坐标为 ( x max , y max , z max ) (x_{\text{max}}, y_{\text{max}}, z_{\text{max}}) (xmax,ymax,zmax)。
步骤 1:计算矩形的每个轴的长度
- 对于 X 轴:矩形的长度为 L x = x max − x min L_x = x_{\text{max}} - x_{\text{min}} Lx=xmax−xmin
- 对于 Y 轴:矩形的长度为 L y = y max − y min L_y = y_{\text{max}} - y_{\text{min}} Ly=ymax−ymin
- 对于 Z 轴:矩形的长度为 L z = z max − z min L_z = z_{\text{max}} - z_{\text{min}} Lz=zmax−zmin
步骤 2:归一化坐标
假设我们有一个点 P ( x , y , z ) P(x, y, z) P(x,y,z) 位于矩形内,其坐标需要转换到一个标准化的坐标系中,范围从 0 到 1。
- 归一化后的 X 坐标 x ′ x' x′ 计算为:
x ′ = x − x min L x x' = \frac{x - x_{\text{min}}}{L_x} x′=Lxx−xmin - 归一化后的 Y 坐标 y ′ y' y′ 计算为:
y ′ = y − y min L y y' = \frac{y - y_{\text{min}}}{L_y} y′=Lyy−ymin - 归一化后的 Z 坐标 z ′ z' z′ 计算为:
z ′ = z − z min L z z' = \frac{z - z_{\text{min}}}{L_z} z′=Lzz−zmin
举个例子
假设矩形的最小角点坐标是 ( 2 , 4 , 6 ) (2, 4, 6) (2,4,6),最大角点坐标是 ( 8 , 10 , 14 ) (8, 10, 14) (8,10,14),矩形的尺寸为:
- L x = 8 − 2 = 6 L_x = 8 - 2 = 6 Lx=8−2=6
- L y = 10 − 4 = 6 L_y = 10 - 4 = 6 Ly=10−4=6
- L z = 14 − 6 = 8 L_z = 14 - 6 = 8 Lz=14−6=8
假设有一个点 P ( 4 , 8 , 10 ) P(4, 8, 10) P(4,8,10),我们需要将这个点的坐标归一化到 [ 0 , 1 ] [0, 1] [0,1] 范围。
- 归一化 X 坐标:
x ′ = 4 − 2 6 = 2 6 = 0.3333 x' = \frac{4 - 2}{6} = \frac{2}{6} = 0.3333 x′=64−2=62=0.3333 - 归一化 Y 坐标:
y ′ = 8 − 4 6 = 4 6 = 0.6667 y' = \frac{8 - 4}{6} = \frac{4}{6} = 0.6667 y′=68−4=64=0.6667 - 归一化 Z 坐标:
z ′ = 10 − 6 8 = 4 8 = 0.5 z' = \frac{10 - 6}{8} = \frac{4}{8} = 0.5 z′=810−6=84=0.5
所以,点 P ( 4 , 8 , 10 ) P(4, 8, 10) P(4,8,10) 归一化后的坐标为 ( 0.3333 , 0.6667 , 0.5 ) (0.3333, 0.6667, 0.5) (0.3333,0.6667,0.5),这表示该点在矩形内相对于最小角点的位置。
通过这种方式,所有矩形内的点都会被映射到标准化的坐标系中,便于后续的计算和比较。
"这没有任何意义"
在归一化坐标时,应该使用矩形的最大坐标减去最小坐标来计算每个轴的长度。这个长度用于归一化每个点的坐标,而不是使用错误的"长度"概念。正确的做法是,对于每个轴,计算最大坐标和最小坐标的差值,并将每个点的坐标减去最小坐标后再除以这个差值,得到归一化后的坐标。
game_math.h: 引入 GetBarycentric
为了简化操作,可以实现一个函数,用来计算点相对于三维矩形的归一化坐标。这个函数可以接受矩形的最小角点和最大角点坐标,以及需要归一化的点,然后按照以下步骤处理:
- 对于每个坐标轴,计算该轴的长度,即最大坐标减去最小坐标。
- 将点的坐标减去最小坐标。
- 然后除以该轴的长度,得到归一化后的坐标。
这个过程非常直接,基本上就是减去最小坐标后除以矩形的长度。不过需要注意的是,如果矩形在某个轴上的尺寸为零(即最小坐标等于最大坐标),就会导致除以零的情况,这样就会产生无效的值。因此在实现时,要加入检测和处理这种特殊情况,防止除零错误。
总的来说,这个功能的实现很简单,但需要特别处理某些边缘情况,以保证代码的健壮性。
引入 SafeRatio 函数
为了增强代码的健壮性,提出了一种"SafeRatio"函数。该函数的作用是,在进行除法运算时,确保分母不为零。如果分母不为零,就进行正常的除法运算;如果分母为零,则返回一个预设的值(例如0或1),以避免除零错误。
具体来说:
- 安全比例函数接收两个参数:分子和分母。如果分母为零,函数返回一个预定义的默认值(通常是0或1),否则执行除法。
- 预设值的选择通常取决于实际需要,例如,当发生除零时,可以返回0或1。
- 这种方式非常适用于那些对性能要求不高、但需要增强代码稳定性的场景,特别是在处理可能出现错误的维度值时,能够避免程序崩溃。
通过这种机制,可以确保即使在某些维度值为零时,仍然能返回合理的结果,避免程序出错。
在 HandleOverlap 中使用 GetBarycentric
为了处理坐标变换和区域定位,首先获取区域的"中心坐标"并计算该坐标在矩形内的位置。对于这个过程,使用的是类似于"重心坐标"的方式,通过一个函数将位置转换为0到1之间的值,表示该点相对于矩形的位置。可以对这些值进行断言,确保它们在0到1的范围内。如果传入的点在矩形之外,则这些坐标不会被限制。
接下来,可以将得到的重心坐标中的Y值作为代表高度的依据,来确定当前点在"楼梯"上的位置。通过Y值的变化,能够判断角色是处于楼梯的哪个位置,从而进行相应的地面高度调整。最终,利用Y值来执行线性插值,从而计算出角色与楼梯的接触位置。
Blackboard: Lerp
在讨论插值时,使用了一个常见的线性插值公式: ( 1 − c ) ∗ t s t a r t + c ∗ t e n d (1 - c) * t_{start} + c * t_{end} (1−c)∗tstart+c∗tend,其中c
是介于0到1之间的值。这个公式根据c
的值在两个数值之间进行插值,c
为0时结果为 t s t a r t t_start tstart,c
为1时结果为 t e n d t_end tend。这个公式通常用于在两个值之间按比例移动,可以用于计算在楼梯上移动时的地面高度。
为了计算地面位置,将重心坐标中的Y
值作为c
值,表示在楼梯上位置的百分比。接着,根据Y
值和开始和结束位置的Z
值进行插值,来确定当前位置对应的地面高度。插值函数(例如Lerp)会根据Y
的值线性地计算地面高度。
此外,可以考虑将插值方向进行控制,例如使插值按上升或下降方向绘制,从而使其在视觉上更为清晰。
game_math.h: 引入 Lerp
在插值函数中,使用了一个常见的线性插值公式: ( 1 − t ) × a + t × b (1 - t) \times a + t \times b (1−t)×a+t×b,其中 t t t 是介于 0 到 1 之间的值,表示在两个值之间按比例计算。该函数根据 t t t 的值,能够返回从 a a a 到 b b b 之间的插值结果。
这个插值公式的作用是:当 t = 0 t = 0 t=0 时,返回 a a a;当 t = 1 t = 1 t=1 时,返回 b b b。对于介于 0 和 1 之间的 t t t,返回的值就是在 a a a 和 b b b 之间的一个加权平均值。
该函数可以用于实现例如计算物体在某一位置上变化的值,或者处理在某个区间内按比例过渡的效果。
game_sim_region.cpp: 在 HandleOverlap 中使用 Lerp
通过使用重心坐标(barycentric)来进行插值,目标是在线性插值中使用Z轴的最小值和最大值。首先,Z轴的最小值是区域矩形(region rectangle)的最小Z坐标,而最大值则是区域矩形的最大Z坐标。然后,通过在这两个Z值之间进行插值,来计算地面的位置。
具体操作是,基于区域矩形的Z轴范围,通过重心坐标的Y值来进行线性插值,进而确定地面位置的Z值。这样,人物总是会"钉"在地面上,位置会随着插值结果自动调整。
由于当前的渲染系统没有很好的支持这一过程,尽管地面位置的计算已经完成,人物还是会自动地"吸附"到地面上。
在 CanCollide 中处理楼梯碰撞
在处理楼梯井的重叠时,需要确保移动者不会与楼梯井发生碰撞。为了避免这种情况,必须设置一个条件,使得楼梯井区域不会参与碰撞检测。因此,重叠检测的结果应设为"false",即在碰撞系统中不考虑楼梯井区域的碰撞。
运行游戏并尝试走过楼梯
在处理楼梯井区域时,发现碰撞检测仍然发生,是因为没有正确排序两个实体。在这种情况下,需要确保检查顺序正确,并且如果实体是楼梯井类型,应该让其通过碰撞检测。
调试: 进入 HandleOverlap
现在,理论上应该能够检查楼梯井的工作状态,看看它们是否正常运行。接下来,需要一种方法来渲染位置和Z值,这可能是下一步的任务。
在此之前,可以先检查是否正确处理了楼梯井的碰撞。可以通过直接检查代码的运行情况,看看是否如预期般工作。由于刚刚写了很多代码,所以需要通过实际测试来验证其效果。
目前的关键是确认是否在启动时就触发了楼梯井相关的碰撞检测。
game_sim_region.cpp: 检查物体是否重叠自身
当前的问题在于,代码没有检查物体是否与自身发生重叠。为了修复这一点,需要在重叠检查时加入条件,确保物体和自己不会被认为是重叠的。这样可以避免处理不必要的重叠情况,并确保逻辑正确处理不同物体之间的碰撞。
game.cpp 和 game_sim_region.h: 引入 EntityFlag_Moveable
在处理楼梯井时,出现了一个问题,所有物体都在被移动,而实际上像楼梯井这样的物体应该是固定的,不需要调用MoveEntity
函数。为了解决这个问题,应该为物体添加一个movable
标志,用来标记哪些物体是可以移动的。只有设置了movable
标志的物体才会被移动,这样就避免了不必要的移动操作。
此外,标志的添加方式需要仔细处理,确保每个物体在初始化时都有正确的标志设置,这样在后续的处理过程中才能正确地识别哪些物体是可移动的,哪些是不可移动的。
game_entity.h: 将 Flag 函数复数化
在开发过程中,遇到的一个问题是需要管理和清理实体的标志位(flags)。特别是在涉及实体是否可以移动、碰撞和分组时,标志位的处理变得复杂。因此,建议使用 AddFlags
方式来同时设置多个标志位,并确保这些标志位能够有效地反映实体的状态。
例如,对于可移动的实体,可以通过设置相应的标志位来标记该实体是否能够移动;对于碰撞和分组等其他特性,类似的标志位也应一并处理。这种方法使得标志位的管理更加统一和灵活。
另外,在清理和管理标志位时,也需要特别注意碰撞标志(collides)的问题,因为这可能会导致碰撞系统的状态变得不一致。
调试: 进入 HandleOverlap
在调试过程中,发现了一个问题:y值不应该是负数,但实际却出现了负值。这是一个bug,可能与映射或重叠处理相关。
我们已经需要 clamp
发现问题的原因是重叠区域的总值可能会为负,因此需要使用一个(clamp)处理,尽管之前认为不需要这个步骤。最终认识到这个需求是存在的。
Blackboard: 一个物体如何即使它的点不在楼梯中也能与楼梯重叠
发现了一个问题,虽然一个角色可能重叠在楼梯区域,但其位置并不在楼梯内部。因此,需要将值Clamp到矩形的最小和最大范围内,这样可以解决问题。决定进行Clamp处理,并认为这样做不会带来太大影响。
game_math.h: 引入 Clamp 函数
在代码中添加了一个Clamp函数,用于确保值在最小值和最大值之间。通过比较结果与最小值和最大值,确保返回的结果不会超出预定范围。还考虑到未来可能会需要对特定范围(如0到1)进行优化,因此设计了一个专门的Clamp函数来处理这种常见的需求,确保能够正确返回Clamp后的结果。
调试: 进入 HandleOverlap 并确保我们的重心坐标是规范化的
现在应该不会再遇到之前的问题了。检查了一下当前的y值,发现它实际上上升了一些。然后查看了"ground"的值,原本应该是0,但结果显示为-0.52。需要进一步检查这个值是否合理,尤其是检查相关区域的具体情况,特别是z轴的值是否为0,以及该区域的维度。
Blackboard: 定位我们的楼梯
发现问题所在,楼梯的位置似乎不太正确。原本的设置导致楼梯放置位置错误,虽然已经设置为一个水平面,但实际位置似乎偏离了预期。为了修正这个问题,需要调整楼梯的位置。此外,还考虑到楼梯应该覆盖一个较宽的区域,但只有在特定区域内才应用z轴值,因此可能需要为楼梯设置单独的维度,并且调整楼梯的实际位置。
game.cpp: 对 ChunkPositionFromTilePosition 进行偏移,因为它可以
为了调整楼梯的定位,需要对其进行偏移处理。目标是让楼梯的位置不再围绕z轴水平,而是做一个错位处理。通过将楼梯的深度设置为某个值,并通过偏移量将其向上移动一半,使其位于两个z轴层之间的中间位置。这种方式能确保楼梯的位置更加合理。
运行游戏并查看楼梯
为了实现预期的效果,需要更新组合调用,确保在计算位置时考虑到额外的偏移量。这样,偏移量将直接添加到位置计算中,默认情况下使用零向量,方便那些不需要偏移的情况。不过,在检查偏移效果时,发现楼梯没有按预期显示,可能是偏移量设置过大,导致楼梯没有包含在预期区域内。需要重新检查偏移的设置,确保它仍然能够正确地包括在内。
game_sim_region.cpp: 正确根据 ChunkZ 实体
发现问题,在收集所有敌人时,z轴的值并没有正确地处理,实际上是在制造一个无效的z值,这导致了错误。应该在计算时遍历所有在最小和最大z值范围内的区域,而不是依赖之前错误的处理方法。此外,还不清楚如何最初计算这些边界,特别是关于摄像机原点和区域的计算。当前的处理方式是在每个方向上偏移半个瓦片,虽然这可能不是最理想的,但至少做了一些尝试。
运行游戏并同时看到两个楼层
发现z轴的处理实际上已经有一些效果,因为它偏移了敌人,使得我们能够同时看到上下两个层次的实体,这是意外的效果。现在可以同时收集上层和下层的实体,虽然最初没预料到会发生这种情况。不过,理论上应该可以在上下两个层之间移动,但目前还不确定是否真的能做到。尝试通过阻挡的区域时,发现不能通过,而另一个方向的通过性也还不确定。
调试: 进入 HandleOverlap 并发现我们没有通过测试
为了找出无法上下移动的原因,需要检查重叠测试部分。当前的重叠测试似乎没有正确地检测到重叠,因此可能是因为我们测试重叠的方式过于严格。为了正确地进行包含性测试,应该扩大检测范围,特别是当角色位于z轴的某个值上时,测试应该能够通过。为了进一步调试,决定设置断点并查看 move entity
调用时的重叠测试,分析为什么测试失败。
调试: 调查为什么包含测试失败
在处理碰撞检测时,发现英雄实体稍微低于Z线,因此没有与楼梯发生碰撞,这是因为它尚未被裁剪。楼梯是向上的,而英雄位于z=0 的位置。这个问题比较复杂,涉及到实体在 z 轴上的位置和裁剪的处理。
Blackboard: 没有 Ground 概念的危险
在处理地面支持时,遇到了一些复杂性。没有明确的地面概念,角色只是沿着地面行走。因此,考虑到是否需要在测试开始时确定地面支持,基于此来处理角色的运动。可能之前的做法是将 z 轴集成在内,但现在觉得可能是个错误。或许不应该将三维更新结合在一起,而应该分开处理,先处理二维更新,之后再单独处理 z 轴问题。总之,当前的解决方案感觉有些不稳定,需要更智能的地面处理方式。
game.cpp: 暂时将楼梯做大
一个简单的解决方案是将楼梯稍微做大,这样就能暂时解决问题,但这种方法显得有些不太理想。虽然这样做可以立刻修复问题,但并不是最佳选择。需要找到一个更好的地面更新方案,以解决当前的问题。
运行游戏并发现我们正确进行 lerp
现在系统已经能够正常工作了,尝试通过楼梯走动,但仍然不确定楼梯的方向是否正确。通过检查地面值,发现应该是零,验证了系统没有按预期向上移动。继续测试,观察发生了什么情况。
game_sim_region.cpp: 使用我们的 Ground 值
还有一件事需要完成,才能彻底解决问题。需要使用地面值来处理这个问题,确保系统按预期工作。
没楼梯的问题是MaxChunkP.ChunkZ值不对
运行游戏并发现我们离目标更近了
楼梯目前确实是向上移动的,尽管无法渲染,无法直观地看到,但可以确认实体是向上移动的。然而,存在一个问题,无法通过某些区域,可能是因为这些实体没有进行三维碰撞测试。接下来需要处理的是确保这些实体能够进行三维碰撞测试,解决这个问题后,系统应该会更接近完成。
在这种没有真实三维的环境下,系统使用了类似伪三维的方法,这为处理碰撞和物体行为带来了挑战,但也提供了有趣的探索机会。现在的问题是,实体仍然会阻挡,碰撞检测仅限于二维,因此无论实体位于多低的位置,都不会正确地进行碰撞测试。
game_sim_region.cpp: 另外测试实体的 Z 平面以确定是否可以碰撞
可以将其视为一种特殊的情况,假设实体位于相同的 zy 平面上时,它们可以被认为发生了碰撞。如果它们不在同一平面上,则不能发生碰撞。这样做的目的是确保只有在特定条件下,实体才会被视为有碰撞。
运行游戏并发现我们接近目标
在目前的进展中,角色的下落是由重力控制的,而不是主动地将角色移动到下方。实际上,角色是"下沉"进入楼梯,这也解释了为什么角色跑过楼梯时不会自动下去。这个机制需要进一步调整,使其更像真实的楼梯,避免出现角色不自然地悬浮在空中的情况。
此外,我们还需要解决一个问题:当前的处理方式让角色看起来像是在快速穿越楼梯,而没有显现出正常的下落过程。为了改进这一点,需要考虑如何正确处理角色在楼梯上的运动,确保它不再显得不自然或荒谬。虽然这一切仍然存在许多问题,但至少我们已经取得了一些进展,尽管这个过程还很不完美。
总的来说,虽然进展较慢,且系统仍有一些不合理的地方,但我们已经朝着正确的方向前进了。
familiar的角色是否能够穿越楼梯或被瞬间传送到楼层?
关于楼梯的设计,仍然存在疑问:角色是否应该直接穿越楼梯到达下方,还是应该像普通角色一样逐步走过楼梯?目前的想法是,除非角色可以飞行,否则所有角色都应该需要使用楼梯。对于某些飞行类角色,可能会有特殊机制,允许它们穿越不同的区域或楼层,但这部分设计还没有最终决定。
总之,尽管有一些不确定性和问题,但我们离解决这些问题已经不远了,明天会继续探讨和解决。
我们什么时候做精灵前景和背景的优先级?(我不知道这个叫什么)
关于优先级和图层排序的问题,实际上这涉及到的是排序和遮挡处理。虽然这个过程并不复杂,基本上只需要在渲染时对实体进行排序。可以通过简单的排序算法来实现,比如使用C语言中的快速排序函数qsort
。如果不想自己写排序函数,也可以直接利用现有的库。
当前的实现方式是,将所有需要渲染的对象存放到一个数组中,然后对它们进行排序,最后按顺序绘制。这个过程本质上很简单,只是因为我们还没有专门写一个排序的例程,所以暂时没有进行。
实际上,这个功能并不是开发的核心部分,因此还没有在当前工作中实现。不过,一旦需要处理排序时,方法其实很直接,只要将物体按需要的优先级排序,再依次渲染即可。
你提到过可以从下方/上方攻击。那在楼梯上也能做到吗?
关于攻击的设计,考虑到游戏中的角色和敌人,攻击从下方向上发起似乎是不合适的,尤其是在平面上。在某些特殊情况下,比如面对一个巨大的BOSS,可能会允许从下方攻击,但通常不希望出现从上方攻击下方敌人的情况,因为这种设计总给人一种不自然的感觉。
如果攻击是通过掉落物体或将物体从平台上丢下去,可能是可以接受的,但总的来说,攻击最好还是局限在同一平面内,这样会更符合直觉。
我刚开始看你的视频,但你已经讲过/编程过实体系统。你会在这里用到它吗?
目前我们还没有讨论系统,也没有深入探讨实体如何相互互动。到时候,系统将成为游戏开发的一个重要节点,因为一旦开始开发这个系统,就意味着游戏的主要内容将进入最终阶段。在此之前,已经完成了空间处理、实体更新和碰撞检测等核心部分。
能量系统的开发将与游戏的其他部分并行进行,逐步扩展和完善。这个过程将直接影响到游戏的整体结构,因此需要慎重规划。
这个应用的范围是否包括移动的环境,所以你需要引入逻辑来检测资产的移动?
这个应用的范围确实包括了动态环境的元素。随着开发的推进,碰撞系统将进行重大升级,特别是在所有核心功能完成后,移动环境将是一个可以加入的特性。实际上,可以将这一功能列入待办事项,未来可以尝试实现移动物体之间的交互,比如物体推动其他物体的机制。
游戏会为不同区域做色彩分级吗?例如英雄穿的蓝色和黑色披风,在某些光照条件下可能会显现为其他颜色?
游戏中的颜色变化并不是由于光照条件的变化,而是与表面反射率有关,这和所谓的颜色渐变(color greeting)无关。光照白平衡可能会影响颜色表现,但并不能让蓝色变成金色。如果出现这种现象,通常是因为相机的白平衡设置极端失调,虽然在某些极端情况下,这种情况是可能发生的,但实际上它更像是白平衡和反射率的组合问题,而非光照本身的影响。
什么是"Piece Groups",它们在精灵渲染中的当前使用或作用是什么?
当前,"peace groups" 是一种临时的方式,用于帮助快速组装和渲染精灵。每个"piece group"基本上是将多个部分(如角色的躯干、披风、头部)组合在一起的工具。现在,它们不仅帮助将不同部分拼接到一起,还记录哪些部分需要渲染。
不过,未来的渲染系统将不再依赖"peace group"。它们可能会被更为精确的坐标系统取代,渲染过程本身将承担记录和处理所有渲染需求的任务,并负责排序、剔除和其他渲染相关的操作。这意味着,"piece groups" 将不再作为独立的组存在,而是将由渲染系统统一处理这些功能。
敌人会跟着你上下楼梯吗?如果会,如何处理镜头外的空间?
敌人可以跟随角色上下楼梯,这个设计是可行的。至于镜头外的空间,它将与镜头内的空间一样运作,甚至可能更好。这意味着即使不在视野内,空间的处理方式依然保持一致。
你将如何处理精灵动画?你使用了某个库吗?你听说过 Spine 2D 骨骼动画框架吗?
关于精灵动画的处理,计划是主要采用程序化的方法进行实现。虽然曾听说过Spine 2D骨骼动画框架,但并没有深入了解。如果程序化方法无法完全满足需求,可能会考虑使用一个小型的转换工具来辅助生成动画。但是,目前倾向于相信可以通过程序化实现所有的动画,因为这样不仅可以达到需求,还能更好地教授编程技能。相比使用工具制作动画并读取文件,程序化方法能更好地帮助理解编程原理,这对教学更有意义。
那个家伙是谁?
Mike Sarten是一个在技术领域有很多经验的人,曾在Valve工作,也曾在Rad工作。他参与了Steam相关的许多工作,特别是在解决一些复杂的服务器问题,如修复Team Fortress 2和Dota 2的服务器问题。被认为是一个非常擅长处理bug和解决技术难题的人,他有一种能迅速介入并解决问题的能力。
相比之下,自己更偏向于从零开始编写代码,创建全新的系统,而不是接手现有的项目并调试修复。虽然自己在遇到问题时可能会有些抱怨和沮丧,但并不擅长像Mike那样在项目中解决复杂的bug和技术难题。
我的意思是有不同的楼层,这会有不同吗?我假设不会
关于敌人是否能上下楼梯的设计,虽然从游戏玩法角度来看并没有强烈的必要性,但决定实现这个功能是因为它增加了编程的难度。这种设计将强迫开发者处理更多复杂的系统,从而也让大家能够学到更多的编程技巧。尽管这会让开发变得更复杂,但如果在未来发现时间不够用并需要删减功能,像敌人上下楼梯这样的特性是可以被削减的。所以,最终的决定是:既然它会增加编程的挑战,就让敌人可以上下楼梯。
你能运行这个游戏让我们看看它当前的状态吗?
目前还没有完成游戏的主要内容,主要集中在空间划分、敌人碰撞和引擎的基础开发上。虽然没有真正的游戏展示,但已经开始实现一些基本的功能,比如可以创建房间并在它们之间行走。我们还没有进行渲染优化,所以会遇到像物体排序的问题。暂时使用的是一个临时的渲染系统,导致物体可能会出现被遮挡的问题。
目前实现的功能包括添加了投射物、敌人跟随、攻击机制和生命值管理,虽然这只是一些简单的测试,真正的游戏玩法还没开始实现。整体目标是从零开始编写整个游戏,而不是直接使用Unity或其他工具。我们正在专注于构建游戏的架构,并展示如何从头到尾编写游戏的各个部分。
虽然进展比较慢,目前只有74小时的编码量,但已经取得了一些进展,接下来会继续按照计划推进开发,包括调试系统、音频混合、资源流式加载、AI路径规划等功能。
好吧,你知道我们有摄像机空间,而外面有一些我们看不见的区域,但实体仍然在移动吗?我们需要编辑代码来使其适应"垂直"楼层吗?没关系,如果你不明白,我可能想得太远了
目前在游戏中,摄像机视野内外的空间没有区别。虽然有些实体在视野外活动,但它们的行为和代码与视野内的实体一样。唯一的区别可能是,视野外的空间更新频率较低,但使用的代码是相同的。因此,怪物上下楼梯的代码在视野内和视野外都能正常工作,直到达到性能瓶颈时,可能需要对不可见部分做出特殊优化。
我们会使用固定的、离散的动画来像《塞尔达传说:过去的连结》那样上下楼梯,还是目标是使得移动无缝?
角色的移动,尤其是上下楼梯的动画,应该是无缝的,不允许有任何中断或自动播放的动画。角色始终应由玩家直接控制,除非是受到限制的情况下(例如被怪物抓住)。这种设计要求玩家能够自由控制角色的每个动作,避免在不需要的情况下强制播放动画,因为这样会让玩家的体验变得非常不愉快。
现在代码行数是多少?
目前的代码量大约是4500行。由于在编码过程中需要讲解每个细节,所以开发速度比较慢,平均每小时写大约50行代码。