游戏引擎学习第60天
总结并规划今天的内容
计划继续完善一些功能。系统已经启动,目前的状态显示程序正在正常运行,但部分功能还存在需要改进的地方。
当前的实现包括:
-
familiar功能:
- 现在有一个“小familiar”会跟随我们,但它的行为逻辑还没有完全实现。
- 未完成的部分是“精灵分类”(Sprite Sorting)。
- 目前对象之间的前后顺序未能正确显示,这导致视觉效果存在缺陷。
- 虽然这是一个小问题,但不会影响渲染系统的核心逻辑,后续完整渲染实现时会解决。
-
当前问题的描述:
- 现有跳跃阴影的显示是错误的,需要修复。
- 测试躯干实体(一个简单站立的实体)目前功能单一,计划赋予更多交互能力。
-
计划改进:
- familiar行为增强:让familiar与玩家保持一定的活动范围,不再无限接近或远离。
- 敌对实体功能:
- 测试躯干实体将获得简单的攻击逻辑,例如向玩家移动到一定距离,或者发射炮弹。
- 这是一个基础的交互机制,用于丰富游戏玩法。
- 生命值系统:
- 给玩家和敌人添加生命值。
- 当生命值降到零时,实体将被视为“死亡”,即从功能上失效。
-
技术细节:
- familiar和敌人的移动范围、生命值系统、以及简单攻击逻辑是当前关注的重点。
- 这些功能的实现将为后续复杂系统(例如交互反馈、视觉效果优化)奠定基础。
下一步将从修复阴影、改进familiar行为和添加生命值系统入手,逐步完善这些基本的游戏机制。
描述“sumo mandarin”
当前工作已经涉及到一个UpdateFamiliar的方法,现在计划进一步扩展为处理怪物的更新逻辑。在此之前,需要先解决跳跃阴影的显示问题,确保其正常工作,这是一个需要立即修复的错误。接下来的任务是理清怪物在游戏中的行为逻辑,并通过新增的“UpdateMonster”方法进行处理。
修复跳跃阴影
在当前的工作中,记录实体可见部分的关键点是明确如何绘制对象的片段。为了实现这一目标,主要使用了一个名为 PushPiece
的方法。该方法在处理过程中存在一个问题,即错误地将实体的高度偏移值合并到了绘制的阴影中,导致阴影未能正确贴合地面。
具体来说,错误的原因是实体的 Z 轴高度被直接应用,而这实际上影响了阴影的定位。阴影本应始终保持在地面上,而不是随实体的 Z 高度变化。因此,需要通过调整系数来解决这个问题。
调整方案包括引入一个系数来区分实体在 Z 轴上的实际高度和阴影高度。例如,通过对 Z 轴值引入一个默认参数来明确阴影的固定高度,确保阴影始终保持在地面。这样,在调用 PushPiece
方法时,可以单独设置阴影的 Z 值为零,从而保证阴影不会随实体 Z 高度变化。
在具体实现中,实验性地在 PushPiece
方法中新增了一个 Z 系数参数,并在调用涉及阴影的代码段中,将 Z 系数设定为零。这种方法虽然不是最终的解决方案,但可以在一定程度上使阴影回归地面,从而恢复跳跃时的视觉正确性。
最后,通过运行代码确认调整后的效果,阴影能够正确地保持在地面上,与期望一致,暂时解决了这一问题。
修改浮动头部阴影的透明度
实现了一个逻辑用于动态调整漂浮物体的阴影透明度(Shadow Alpha),使其随着物体上下浮动的运动呈现出对应的视觉效果。以下是详细内容总结:
-
问题分析
在调整一个漂浮在地面上的头部对象时,阴影透明度需要根据对象的垂直浮动来进行变化。这要求阴影能响应对象的“浮动高度”,从而提供更自然的效果。 -
浮动逻辑的实现
- 通过
BobSin
表示物体上下浮动的符号值,该值在-1
和1
之间波动。 - 利用这个符号值,通过一个乘数对阴影透明度进行调制,从而影响阴影的强度。
- 调制的目的是使当对象向上浮动时,阴影变得更淡;当对象向下浮动时,阴影变得更浓。
- 通过
-
调整和计算细节
- 起始值设为阴影透明度的一半,模拟漂浮的初始状态。
- 使用符号值(
BobSin
)乘以一个系数,来设置透明度在某个范围内的浮动。 - 为了让阴影更自然,符号值的方向需要翻转,即当对象浮动到较低位置时,阴影透明度应增加,而不是减少。
- 通过此翻转,确保了浮动运动和阴影透明度变化方向的一致性。
-
调试和验证
- 在实现过程中发现了方向错误的问题,即阴影透明度在对象向下移动时变得更淡。
- 为了解决这一问题,调整了符号值的运算逻辑,确保当
BobSin
为负时,透明度增加,而不是减少。 - 测试过程中,验证了透明度的变化是否符合预期,确保了阴影在上下浮动时能够正确响应。
-
代码实现要点
- 调用
push piece
时,直接利用偏移值进行计算。 - 在阴影渲染逻辑中,通过引入动态调制逻辑,使阴影透明度的变化与对象的浮动高度直接关联。
- 进一步优化了偏移值与透明度的关联方式,使整个计算过程更加直观。
- 调用
-
改进方向
- 需要进一步调整浮动范围的具体值,确保阴影透明度的变化效果在不同高度范围内都符合视觉要求。
- 可以增加参数化选项,以便在不同场景中灵活控制阴影透明度的动态变化幅度。
通过以上调整,解决了漂浮物体的阴影透明度无法与浮动高度动态匹配的问题,同时为未来类似的动态调整逻辑提供了参考框架。
重申 PushPiece 的单位是像素空间
当前的问题在于(PushPiece)存在于像素空间,而不是按照我们期望的方式工作。这样处理使得它的显示效果变得不美观,尤其是当它在屏幕上向下移动时,变成了正值。虽然如此,由于目前没有真正进行渲染,这个问题暂时不被关注。
这段代码现在主要是用于测试目的,等到渲染处理完成时,代码的行为将会发生很大的变化,应该能够解决这个问题。虽然头部的影子还需要正常响应浮动,但目前已经可以确认其他部分正常工作。最终,决定暂时不再深入这个问题,计划先离开并结束当前的工作。
让familiar在英雄附近稍微停留一段距离
当前的问题是,人物与其他对象的距离太近,导致视觉效果不佳。尝试通过计算最近的英雄距离的平方值来调整人物与其他对象的间距,希望人物能停在距离大约一米远的位置。经过调整后,仍然感觉人物与目标之间的距离过近,因此决定增大间距来测试效果。
在此过程中,发现人物模型的尺寸可能过大,导致距离测量结果看起来有些不合适。原本设定的三米距离似乎显得太近,实际距离应该比三米更远一些。通过调整人物的比例,问题得到了一定改善,最终调整后的设置让人物与其他对象的距离看起来更合适,更合理。
为英雄添加生命值
在此讨论中,我们通过添加生命值(Hit Points, HP)来扩展实体的功能。首先,考虑将生命值存储为单独的结构体,以便在未来进行更复杂的操作,比如处理中毒、脆弱等状态。每个生命值点将具有一个最大值,并且会有一个存储数组来管理它们。每个生命点的状态可能会改变,比如被填满或受到伤害。
在结构体中,我们使用一个数组来存储每个生命值点的状态,可以设定多个生命值区段,例如以四个段为基础来表示生命值的不同填充程度。此外,代码中讨论了如何在绘制时将这些生命值点可视化,比如通过矩形显示当前生命状态。绘制矩形时,位置和尺寸通过坐标和偏移量来确定。
代码实现方面,初始生命值会设定为最大值,并逐渐显示当前的生命点。矩形的渲染包括对尺寸的调整,以适应不同的分段显示方式,如将生命值划分为四个部分。这个方法最终实现了一个简洁的健康状态显示系统。
game.h:定义 struct hit_point
讨论了是否将每个生命点(Hit Point)作为独立的实体来处理。每个生命点可以拥有状态,并可能与其他潜在的属性或事件关联。具体来说,生命点的最大值会存储在一个数组中,而当前生命点的状态和数量也可以存储在一个数组中。考虑到生命点的复杂性,思考是否将它们作为独立实体来进行管理,因为这样可以让生命点拥有更多的属性和交互方式,从而为系统带来更高的灵活性和扩展性。这种做法提供了一种新的结构和管理方式,可以为未来的复杂操作打下基础。
讨论生命值是否应该被实现为实体
考虑将生命点(Hit Points)作为独立的结构体来处理,以便更灵活地管理每个生命点的状态。例如,生命点可以包含是否被填满的信息,以及当前的填充量或状态标志。这样做可以让每个生命点具备更多的属性,比如是否中毒、是否脆弱等特殊状态,这些状态可以根据不同的条件触发。例如,当生命点受到某种特殊事件影响时,它可能变得更加脆弱,或者会触发其他机制。
这种方式的优点是每个生命点都可以拥有更多的属性和状态,使得处理变得更加复杂和灵活。随着结构变得越来越庞大,管理这些属性也可能变得困难和危险。因此,考虑将生命点作为实体可能是一个合理的选择,因为它们能够承载更多的属性和交互逻辑,增加系统的灵活性,但同时也增加了管理和性能上的挑战。
绘制这些生命值
我们计划实现命中点的功能,首先通过添加一个玩家实体来初始化命中点。假设每个玩家开始时有一个最大命中点值,并且这些命中点可能是以某种形式表示的。例如,一个典型的玩家可能从三个心(最大命中点)开始。每个命中点有一个标识(如标记)和一个已填充的数量,表示该命中点的当前状态。
我们考虑每个命中点可能会有多个状态,如填充量(从0到最大值),并且这些状态可能与其他特殊情况(如中毒、脆弱等)相关联。命中点的表示方式可能不仅仅是一个简单的值,还可能包括标记和其他属性,这使得它们在处理时具有更多的灵活性。
命中点的管理可以通过将它们分成多个部分来处理,例如,将每个心划分为四个部分,每个部分可能表示一个填充点。这样,我们可以更细致地管理命中点的状态,尤其是在碰到特殊情况时,如角色受到伤害或者某些命中点消失时。
为了绘制这些命中点,我们将开始绘制矩形,作为健康条的一部分,用来表示玩家当前的健康状态。这些矩形将反映命中点的填充情况,随着健康变化而变化。我们还会考虑如何使用颜色(如RGB)来直观表示这些状态,例如用不同的颜色表示健康值的不同。
在绘制时,还需要考虑坐标和尺寸的计算,确保矩形正确显示玩家的健康状态。我们也计划在未来可能将绘制代码抽象为一个函数,以便在其他地方复用。同时,还会考虑是否支持透明度(alpha值),尽管目前还没有完全实现这一功能。
总的来说,这个过程是为了实现一个可视化的健康计量表,能够实时反映玩家的健康状态,并在游戏中使用。
黑板讨论:如何将生命值居中围绕角色?
在考虑如何处理对象的安置时,首先要解决的问题是如何确定这些对象的位置,尤其是在其排列上存在中心线时。我们可以将该问题看作是一个离散的安置问题。
首先,明确中心线的位置。对于对象的偏移量,可以通过确定每个对象与中心线的相对距离来处理。对于偶数数量的对象,应该在中心线的两边有相等数量的对象,并且它们之间的间隔(用参数 s
表示)是固定的。这样,排列的对象将对称地分布在中心线两侧。
如果对象的数量是奇数,则中心线将穿过其中一个对象,而其他对象将对称地分布在中心线的两侧。在这种情况下,离中心线的距离可以通过简单的计算得出,例如从中心线到最近的对象的距离为 1
,而其他对象则按照固定的间隔 s
进行排列。
通过这种方式,可以有效地确定每个对象的位置。基本的计算步骤如下:
- 如果对象的数量为0,则无需处理。
- 如果对象的数量为1,则该对象应位于中心线上。
- 如果对象的数量为2,则它们分别位于中心线的两侧,且间隔为
s
。 - 对于更多的对象,可以依次计算每个对象的具体位置,计算方法是根据对象的数量和间隔进行调整。
总的来说,通过这种方式,我们能够根据给定的间隔和对象数量,合理地安排它们的位置,确保它们在中心线周围均匀分布。
公式:
( n − 1 ) ⋅ s 2 \left( n - 1 \right) \cdot \frac{s}{2} (n−1)⋅2s
编写代码将生命值绘制到正确的位置
在此场景中,目标是根据实体的最大生命值(HitPointMax
)来决定如何绘制和排列这些生命值的显示。基本步骤如下:
- 获取最大生命值:首先需要获得该实体的最大生命值(
HitPointMax
)。这个值告诉我们最大可以承受的伤害或最多的生命点数。 - 检查最大生命值:确保最大生命值至少为1。如果最大生命值为0,则不需要进行任何绘制或操作。
- 调整最大值:在计算时,需要对最大生命值进行减一(
HitPointMax - 1
),以确保计算时不包括中心位置的生命值。 - 计算间距:确定生命值之间的间距。根据每个生命点的大小以及屏幕单位(像素与米之间的转换关系),计算每个生命点之间的具体间距。
- 确定尺寸:每个生命值的显示尺寸是根据设定的单位(如每米多少像素)来决定的,这意味着需要调整生命值图标的大小和显示方式。
总结来说,整个过程包括:首先确认最大生命值,并根据此值计算显示的偏移量,然后通过调整间距、尺寸和其他参数来绘制出生命值显示。
将 PushPiece 的偏移量改为米为单位
讨论了在游戏开发中处理与健康状态相关的坐标、尺寸、像素和补偿问题。首先提到的是在设置健康状态时,需要考虑不同的单位和尺寸转换。通过对比米和像素,确定了在代码中需要对这些值进行处理,尤其是在不同实体的健康值显示时。接着探讨了如何将健康值显示在屏幕上,包括如何计算每个生命点的间距和如何通过设置不同的颜色来表示生命值的变化。
讨论的核心是如何在游戏状态中有效地存储和访问这些参数,确保可以根据需要快速获取健康值、像素与米之间的转换比例,并通过游戏状态管理它们。提到了将游戏状态与和平组织结合,使得每次都不需要传递这些信息。
还提出了如何通过代码调整这些值,确保健康条的显示准确,使用不同颜色(如亮红色和深灰色)来区分已填充和未填充的生命值。对矩形绘制、像素管理等部分进行了优化,以简化渲染过程。
最终,提出了通过矩形推送的方式来处理显示和渲染,并决定将这些内容整合到游戏状态中。通过进一步细化代码结构和组织,将健康条的显示方式统一,并确保它们能够与其他游戏组件兼容。
运行游戏并观察熟悉者的明显晃动
当前的代码逻辑已经能够根据最大生命值(点最大值)来绘制生命条的各个部分。接下来,我们需要确保所做的更改不会破坏现有的代码或功能。虽然不确定是否有其他影响,但目前的目标是验证这一过程是否能够正确绘制生命条。
减小 BobSin 的乘数
在将单位从米转换为像素后,之前以像素为单位的内容已经转为世界单位。虽然转换后看起来更加符合预期,但健康条的位置仍然可能略显偏高。接下来,需要继续完成健康条的绘制部分。
将生命值放置到正确位置
当前任务是将元素放置在合适的位置上,首先从某个位置开始,并逐步调整其坐标,确保它正确地呈现。在过程中,需要修正一些Y轴的反转问题,确保所有Y轴坐标朝上。为了使位置调整更准确,需要计算间隔量,并在每次绘制时调整元素的间距。在调整过程中,发现了负值的问题,经过计算后已经解决,并且设置了正确的间隔。最终,目标是确保所有元素的间隔合理,并且所有Y坐标能够正确向上移动。
黑板讨论:解释为什么生命值会堆叠在一起
经过思考,发现之前的间隔处理有误,元素之间的距离应该是直接叠加在一起的。为了确保它们之间有适当的间隔,需要调整计算方式。具体来说,应该将维度的一半作为间隔的基准。如果需要更大的间隔,可以在维度的基础上加上一些额外的空间,例如1.5倍的维度。通过这种方式,可以有效地在元素之间留出足够的空间,确保健康条的显示更加合理。
翻转 PushPiece 的 y 坐标
为了修正之前的坐标问题,需要反转y坐标。这个过程意味着需要调整处理方式,特别是对于偏移量的计算。对于y坐标,应该将它进行反向处理,使其朝相反方向移动。通过这种方式,所有传入的坐标值都会向反方向移动。理论上,这样的处理应当能让元素正确显示在预期的位置。
然而,在实现时遇到了问题,偏移量的调整似乎没有达到预期效果,导致元素没有按预期方式向下移动。需要进一步调试和修正,以确保偏移量的反转和y坐标的调整能够正确应用。
统一 PushPiece 和 PushRect
在处理代码时,发现需要将一些修改统一处理,以避免在不同地方出现不一致的问题。为此,决定将相关的功能整合到一个函数中,这样可以确保所有参数都统一处理,避免手动修改时遗漏某些部分。这样做的目的是确保代码逻辑的一致性,减少出错的可能性。
此外,考虑到需要处理的内容比较复杂,决定将一些操作推向一个通用的处理流程,包括涉及到的各种参数、维度、坐标等。通过将这些过程抽象化,可以更高效地管理和调整不同部分的行为。最终目标是保证程序能够正确执行,减少bug的发生。
在测试过程中,尽管做了一些调整,但有些细节仍然没有按预期效果运行,特别是在坐标对齐和偏移量的处理上。通过进一步的调试和修正,希望能够确保所有的计算和操作顺利进行,并最终得到想要的健康计量表等结果。
最后,考虑到时间有限,决定开始添加更多的功能,例如三维向量的处理,并进一步完善程序。
修复跳跃时的生命值显示问题
讨论的重点是关于在跳跃时角色是否应该移动的问题。认为当角色跳起来时,应该避免出现不必要的移动,尤其是考虑到角色可能需要停留在地面上。这种设计思路被认为是合理的,目的是确保角色在跳跃时的行为符合预期,避免出现不合适的移动。
定义基础向量 v3 和 v4
我们正在考虑优化代码以更高效地处理颜色值,特别是在传递和处理RGB颜色值时的便利性。
- 建议引入一种结构,使得颜色值能够以一种更直观的方式表示,例如使用向量来存储和操作这些值。这样可以避免逐一手动传递每个值。
- 通过向数学模块添加类型定义和功能,可以方便地创建与颜色处理相关的新方法。这样,操作更加一致,语义上更易于理解,同时减少冗余代码。
- 引入新的命名方式,使得可以通过多个名字访问相同的颜色值,提供灵活性和便利性。
- 在向量操作中,能够一次性访问所有相关属性,而不需要逐一传递值,例如颜色值可以被直接传递给相关函数,而不是单独指定每个组件。
具体实施计划包括:
- 定义一个三维向量结构,用于表示RGB颜色值,并为这些向量添加别名,使其易于区分和访问。
- 修改代码中涉及到颜色值处理的部分,将其替换为新的向量结构。这样可以统一数据结构,减少复杂度,并提升可读性。
- 对现有的一些计算逻辑进行优化,例如通过向量运算简化颜色值的传递和计算流程。
实际改动示例:
- 在创建新的颜色类型后,可以将原本单独传递的R、G、B值合并为一个单独的向量参数。
- 进一步清理代码中对颜色值的处理逻辑,例如在某些函数调用中直接传递向量,而不是传递独立的数值参数。
- 利用向量操作简化逻辑,消除重复代码,同时提高操作的语义清晰度和维护性。
此外,我们还研究了如何扩展类似向量的操作到其他领域,比如三维空间中的位置偏移计算。这涉及到将现有的二维差异值替换为三维向量,使得空间计算更加一致和清晰。尽管这部分实现需要更深入的思考,但已确定可通过进一步简化和统一操作来提升整体效率和代码质量。
最后,由于时间限制,暂时搁置了一些复杂的实现步骤,但计划在后续工作中详细规划和完成这些部分,包括三维向量的完整引入及其在各种计算场景中的应用。
解释为什么模板不好
我们讨论了模板在代码中的使用问题,尤其是在数学库和向量操作中的应用。以下是主要内容总结:
-
模板的使用场景
模板允许对代码进行参数化处理,比如针对不同类型或大小的向量创建通用代码。但模板的实际价值需要权衡,因为它带来了一系列的潜在问题。 -
模板的常见问题
- 错误信息:模板引入的错误通常更难理解,调试时会增加额外的困难。
- 编译时间:模板可能显著增加编译时间,尤其是在代码库规模较大时。
- 输出大小:模板会增加生成的二进制文件的大小,这也会进一步减缓编译速度。
- 调试工具支持:很多调试器无法很好地处理模板代码,导致代码的可读性和调试体验变差。
-
模板是否必要
- 在多数情况下,使用模板并没有实际的必要。以向量操作为例,直接定义必要的操作,简单复制并调整代码,可以避免模板引入的复杂性。
- 模板的引入需要考虑实际收益。如果收益不足以抵消它带来的成本,就没有使用的必要。
-
替代方案
- 对于向量类等简单场景,直接实现必要的操作符或方法,避免使用模板带来的复杂性。
- 剪切粘贴的错误容易发现和修复,而模板带来的隐性问题可能长期存在且难以定位。
-
需要注意的事项
- 虽然在少数情况下,模板可能提供便捷性(比如需要处理数百种不同大小的向量),但这属于例外情况。对于大部分普通项目,模板会导致更多问题。
- 使用模板应非常谨慎,尤其是在不真正需要的情况下,模板的代价往往被忽视。
-
总结建议
- 谨慎对待模板的使用,避免不必要的复杂性。
- 简单的剪切粘贴或特化实现往往是更好的选择,尤其是在明确需求的情况下。
- 模板可能适合少量的特定场景,但对于大部分常规开发工作,引入模板通常得不偿失。
这种思路能够帮助简化代码库,提升调试体验,同时避免不必要的编译开销和维护难度。
问:创建一个专门的生命值结构是否过于预先规划了?
-
代码预编写的重要性
当前正在进行的工作属于“预编程”(pre-programming)的范畴。所谓预编程,是指在没有完整引擎或实际游戏代码的情况下,提前规划和设计系统的关键功能模块。这种方式旨在为后续的开发奠定基础。 -
预编程的原因和流程
- 实验性代码:在项目初期,由于引擎和游戏尚未成型,具体的使用场景和需求并不清晰。因此,需要通过编写实验性代码来尝试和验证各种功能。
- 迭代开发:实验性代码经过多次快速迭代后,可以初步确定系统所需的核心功能和结构。这种迭代是开发过程的重要组成部分,能够帮助明确需求并优化设计。
- 快速验证:通过快速实现和调整,可以更直观地了解系统的运行方式,从而为正式的功能实现提供参考。
-
预编程的性质
- 非最终代码:预编程的代码并不是实际游戏的最终代码,而是为了解决未知需求的实验性产物。
- 探索和验证:通过实验性代码,可以探索引擎和游戏的潜在需求,并验证设计是否可行。
- 逐步完善:尽管预编程不直接作为正式代码,但它提供了一个可以逐步完善和扩展的基础。
-
面对未知的解决方案
在项目的初期阶段,由于缺乏清晰的用例和目标,必须通过实验和探索来逐渐发现和定义需求。这种探索性质的开发方式有助于快速适应变化,并最终形成一个稳定的系统架构。 -
整体思路
- 以使用场景为导向:尽管没有最终的用例,但仍然可以通过模拟和实验来假设可能的场景,并围绕这些假设进行开发。
- 快速迭代:保持代码简单明了,快速实现功能,通过多次调整完善设计。
- 灵活应对需求:在实验过程中,根据实际需求的变化和发现进行调整,以适应项目的动态发展。
这种开发方式虽然并非传统意义上的“最终实现”,但它是启动和构建项目的重要步骤。通过这种方式,可以更高效地适应未知需求,并为后续的正式开发提供有力支持。
问:为什么不用 HitPointMax = 12
而是划分为 3 颗心和 4 个分段?
-
设计选择
将生命值(hit points)分为12点,直接表示为一个整数值和将其分解为3个心和4个分节,是两种不同的表达方式。当前选择了后者,目的是为了引入一种更加细致的生命系统表示。 -
分节心脏的概念
- 细化表现:分节的心脏系统能够更加细腻地表示生命状态,比如某个心脏分节被破坏、受到影响等。
- 特殊状态:例如中毒的效果,可以分别影响整个心脏或者仅影响单个分节。这种设计为状态效果提供了更多表现可能。
- 属性独立性:通过分节设计,每个分节可以拥有独立的属性,例如不同的抗性、状态效果等,从而丰富了系统的表现力。
-
设计初衷
这种分节设计是一种带有实验性质的想法,旨在尝试一种新的方式来管理和展示生命值状态。这种处理方式可能更加直观地体现生命值的变化。 -
对比另一种方法
如果选择直接使用单一整数(例如12点生命值)来表示生命状态,则整个系统将更加简单,但是可能会失去分节设计带来的表现力。例如,状态效果只能作用于整体生命值,而无法单独影响某个分节。 -
灵活性和扩展性
当前的分节设计为扩展功能提供了更多可能。例如,可以在未来为每个分节单独添加属性或行为(如抗性、状态效果等),从而使系统更加灵活和丰富。 -
总结
这种设计的重点是为了引入更加细腻的生命值表现方式。通过分节心脏系统,可以更直观地展示状态变化,并提供扩展和自定义的可能性。同时,这种设计也为探索新颖的游戏机制提供了空间。尽管直接使用单一数值也可以实现生命值管理,但分节设计在表现力和功能性上具有更大的优势。
问:注意到浮动头部总是在玩家后方,是否会采用深度缓冲(Z-buffer)来处理,还是通过从后到前绘制?
特别是在 2D 游戏或渲染管道中,前向排序(从前到后绘制)与后向排序(从后到前绘制)对性能和视觉效果的影响。以下是核心要点的总结和逻辑梳理:
背景与问题
-
深度缓冲(Z 缓冲)
- 深度缓冲是用于确定像素绘制顺序的技术,通常比较像素的 Z 值(深度)来决定覆盖关系。
- 在传统 3D 渲染中,Z 缓冲帮助决定哪个三角形或物体应该在视图中可见。
-
排序的重要性
- 在 2D 渲染中,物体可以基于其屏幕位置(Y 值)或自定义的 Z 值进行排序,以正确显示遮挡关系。
- 排序方式对性能和视觉质量有重要影响。
-
半透明渲染的挑战
- 半透明对象需要特殊处理,因为它们会显示其后方的内容。直接使用 Z 缓冲排序可能无法正确渲染半透明区域的混合效果。
两种排序方式
后向排序(Back-to-Front)
- 优点:
- 简化了半透明渲染,可以逐层叠加混合,无需额外的存储或复杂逻辑。
- 视觉效果更易于控制,因为靠前的对象总是覆盖靠后的对象。
- 缺点:
- 每次都需要处理所有像素,可能降低性能。
- 如果没有对顺序进行优化,可能导致过多的填充操作和内存带宽消耗。
前向排序(Front-to-Back)
- 优点:
- 利用 Z 缓冲的特性,大幅减少不必要的像素处理(比如完全被前景遮挡的区域)。
- 提高内存和填充效率,尤其是在处理大量遮挡的场景时。
- 缺点:
- 对于半透明对象,处理逻辑会复杂得多,需要目标 Alpha 通道来管理剩余透明度。
应用与优化建议
-
使用 Z 缓冲的方式:
- 可以开启深度缓冲,但不依赖其自动排序,而是结合前向排序优化性能。
- 半透明对象可以单独进行后向排序,从而确保正确的视觉效果。
-
性能优化:
- 前向排序对遮挡区域较多的场景非常有效,减少无用计算。
- 对于需要混合的对象,明确分离不同渲染阶段(不透明和半透明),有助于避免复杂的 Alpha 逻辑。
-
视觉质量优化:
- 如果渲染目标需要高质量的混合效果,后向排序更为直观可靠。
- 确保渲染顺序与期望的视觉层次保持一致。
结论
根据讨论,为实现最佳性能和视觉效果:
- 优先使用前向排序进行不透明对象的渲染,以充分利用深度缓冲的早退出特性。
- 对于半透明对象,使用后向排序,以确保混合效果正确。
- 通过合理的渲染管道设计,可以避免复杂的 Alpha 管理,同时提升效率和质量。
问:将来是否会有反思时间,提供引擎组件的高层次概述,包括它们的功能和必要性?
我们未来会有一个反思的时刻,回顾目前所遇到的引擎组件,并进行一个high level的概述。这将帮助我们理解这些组件提供的功能以及它们的必要性。只有在尝试实际开发和构建某些功能之后,我们才能真正明确引擎组件的定义以及我们对它们的需求。
当前的目标正是为了达到这一反思时刻。通过逐步尝试实现实际目标,我们正在积累经验,探索引擎需要实现的各项功能。虽然现在还未完全理解引擎组件的所有细节,但这一过程的目的正是为了揭示这些组件的用途。
目前我们每天仅投入一个小时,因此总开发时间实际上相当有限。到目前为止,我们已经花了一些时间构建基本的框架和原型。例如,最初的部分时间用来搭建了一个冬季环境的双平台结构,接下来则专注于原型设计。虽然尚未耗费太多时间在这方面,但通过这些努力,我们逐渐明确了引擎所需解决的问题和面临的挑战。
随着时间的推移,我们会持续完善所有的组件。当所有部分逐步清晰时,我们将进行全面的总结,分析每个组件的作用并设计出一个能够高效实现这些功能的引擎。当前的开发目标正是为了完成这些准备工作,并为后续的系统设计奠定坚实的基础。
问:实现基于房间的平滑滚动效果会很酷,您会考虑实现吗?
我们正在思考一种结合平滑滚动的场景和基于房间的摄像机切换方式的设计方案。这种设计可能在室外场景中实现平滑滚动,而在室内场景中采用基于房间的固定视角切换。
基于房间的视角设计令人感兴趣,尤其是因为它能够带来独特的屏幕锁定体验。这种方式类似于经典的《塞尔达》系列游戏中的地下城设计,玩家在不同房间之间移动时,画面会进行切换,营造出紧凑而独立的战斗或探索环境。
我们考虑的设计是,在室外世界中采用平滑滚动的方式,让整个地图流畅移动,而在地下城等室内区域,则基于房间进行切换,每个房间形成一个独立的挑战或事件空间。这样既保留了世界探索的开放感,又能突出地下城的独立性和沉浸感。
我们希望确保引擎能够支持这两种模式,提供灵活的实现方式,便于开发过程中自由选择最适合的设计。同时,这种双模式的支持也能展示出引擎在处理不同场景中的灵活性和能力。尽管我们尚未决定最终的游戏内容会具体使用哪种方式,但目前的目标是实现对两种模式的兼容,为后续设计提供更多选择。
问:您提到的半透明 UI 难度大吗?是使用相同 Z 缓冲区写 UI,还是分开的?
关于半透明效果的实现,通常情况下,排序相对容易,因为已经确定了绘制的顺序。这种情况下,由于元素本身的层级结构明确,可以直接按顺序绘制,而无需依赖 Z 缓冲器来管理层次。
然而,在某些情况下,Z 缓冲器仍然可以用作加速工具,但不是必须的。例如,对于需要快速判断哪些元素位于哪些位置上的操作,Z 缓冲器可以起到辅助作用,但主要的排序工作通常可以通过明确的结构直接完成。
在关于场景设计的讨论中,我们提到了一些偏好。锁定视角的房间设计具有独特的吸引力,因为这种方式可以提供清晰的区域划分和更集中的游戏体验。而滚动视角虽然也有其魅力,但可能并不适用于所有场景。我们认为两者各有优劣,并希望在未来的设计中找到一种平衡,例如在不同类型的地图中灵活应用。
关于接下来的工作,目前没有发现与已有内容直接相关的问题,因此今天的任务可能会提前结束,为其他事项留出时间。同时,对于地图角色的设计和房间滚动的具体实现,我们仍在探索合适的方案,期望能找到既兼顾效果又简化实现的方法。
问:是否会实现更多的内存处理代码?如果是,会涉及哪些内容?
内存管理通常不是一个需要过多关注的问题,尤其在游戏开发中,很多任务可以通过堆栈和自由列表来实现。这些方法既简单又可靠,而且易于使用。在开发过程中,内存管理更多的是通过结构化的方式来处理,像是通过栈来管理数据,通常不需要使用复杂的 Z 缓冲器或其他高级方法。
对于游戏开发来说,常见的内存管理方法,例如堆栈和自由列表,通常足以满足大多数需求。这些方法实现简单且稳定,能有效避免内存泄漏或性能问题。相比之下,一些更复杂的内存分配方式,如每帧分配大量字符串,通常会引入额外的性能负担和内存泄漏的风险,导致程序出现不必要的麻烦。
即使在更复杂的场景中,内存管理也不应该是主要的关注点。通常,游戏开发者会集中精力在其他更重要的方面,而内存管理工作通常可以通过一些简单且高效的手段来完成。到目前为止,已完成的内存管理工作大约是最终所需的一半,剩余的部分主要是管理栈等数据结构。
问:如果使用房间边缘缩进一定距离的边界框,是否可以通过玩家与边界框的碰撞触发平滑滚动到下一个房间?
在设计游戏时,使用边界框来限制玩家的视野是一个常见的做法。可以设置一个边界框,将其从房间的顶部向下缩进一定的距离,用来控制玩家在接近下一个房间时的碰撞检测。这可以帮助平滑地过渡到下一个房间。
然而,有一个重要的设计理念是,玩家在进入房间之前不应该看到相邻房间的内容。为了实现这一点,避免使用平滑滚动的方式,而是采取瞬间切换的方式,像传统的《塞尔达》游戏那样,在进入下一个房间时,玩家的视角会“卡住”并锁定在该房间内。这种方式能更好地保持游戏的沉浸感,避免在地牢等区域中使用平滑滚动,这样的设计更加符合经典游戏的风格。
问:如何处理分辨率差异?
在处理分辨率差异时,2D游戏的挑战相对较小,主要依赖于在几个主要显示器上良好的运行表现。艺术资源通常在设计时会根据某一特定分辨率进行优化,确保在预定的显示条件下运行良好。当游戏发布时,艺术资源通常会针对大约4K分辨率进行安全处理。
对于精灵游戏而言,性能主要与填充率有关,因为这类游戏并不依赖大量的多边形或顶点,更多的是依赖内存带宽。如果机器具备较高的内存带宽,它应该能够很好的支持4K分辨率的运行。
然而,实际上,无法在所有情况下做到完美的适配分辨率,尤其是当艺术资源设计时已经决定了一个特定的目标分辨率。在这种情况下,艺术资源可能无法做出太多调整,以适应不同的分辨率。这是当前技术条件下的限制,无法通过其他手段改善。
问:目前的错误处理和日志记录系统是什么样的?未来可能会有什么变化?
目前没有实施任何错误处理日志。尽管曾经做过一些简单的调试输出,但这些都不是很完善,只是为了调试而输出的一些调试字符串。现阶段主要进行的是一些简单的代码工作,并不专注于构建复杂的错误处理或日志系统。计划在未来某个时点为特定的功能加入更多的日志记录和错误处理功能,但这一部分工作会稍后进行。
现在的代码主要处于测试阶段,并不追求代码的完全鲁棒性,因此并不需要复杂的调试或错误处理机制。
问:塞尔达在房间切换时有滚动效果,您会考虑类似设计吗?
关于将32位游戏引擎移植到64位环境,是否支持超过4GB的内存分配以及如何处理可执行文件的拆分,这似乎是一个复杂的话题。有些游戏可能会将可执行文件拆分成多个部分,以便支持更大的内存使用(如超过4GB),但这并不是很常见,也未曾听说过这样的具体案例。对这种做法的具体细节并不熟悉。
在游戏过渡方面,类似《塞尔达》游戏中,玩家在房间间过渡时会看到平滑滚动的效果,但这里的目标是希望像《Binding of Isaac》那样,当进入一个房间时,玩家会立刻看到整个房间的布局,并且进入后会被锁定在房间内,无法立即看到下一房间。
问:可以简要讲解一下从游戏启动到运行代码的内存管理吗?
目前的内存管理非常简单。在游戏启动时,首先会分配一个巨大的内存块,这个内存块的大小是根据游戏的内存需求来确定的。例如,如果游戏需要2GB的内存,那么就会分配一个2GB的内存块。
在这块内存中,游戏会将需要的内容放入一个永久部分,这部分内存存放着实体、瓦片块等必要的数据。内存还被分为两个主要部分:一个是过渡部分,另一个是永久部分。永久部分用于存储所有游戏需要持续存在的数据,而过渡部分则用于存储一些临时的数据,例如计算时用到的堆栈和可随时重新加载的资产(如声音等)。
内存中的瓦片块和实体存储使用了一个"空闲列表"。当需要新的实体或瓦片块时,程序会从这个空闲列表中获取。如果空闲列表中没有足够的空间,就会增加新的空间来满足需求。这个空闲列表就像一个链接列表,指向下一个空闲的内存块。每当需要一个新的实体时,程序会从这个空闲列表中分配一个内存块。
至于内存释放,目前没有涉及到复杂的内存回收机制,除非是针对瓦片块的占用,其他部分的内存都不进行释放。对于大多数情况,内存分配通过堆栈和空闲列表来管理,这种管理方式非常简洁有效。
总的来说,这种内存管理方式非常适合2D游戏开发,提供了足够的灵活性来管理内存分配,且不需要额外复杂的处理。
问:为什么光栅图像无法调整大小?为什么绘图必须光栅化而不能用矢量纹理?
光栅化是绘图过程中的一种常见技术,主要是因为它比矢量图像处理更高效。在使用矢量图像时,需要处理大量的向量元素,尤其是当要求高保真度时,处理起来会非常慢。虽然可以将矢量图形分割为凸形状并进行三角测量加速处理,但这种方式仍然比使用光栅图像要慢得多。纹理则是预先定义的像素集合,绘制时只需从这些像素中提取样本并绘制出来,这使得它们能够快速渲染。
使用矢量图像需要更多的计算和资源,以达到相同分辨率下的保真度,因此对于大多数应用来说,光栅图像更加实用,尤其是在不需要进行大规模缩放的情况下。监视器分辨率的增长较慢,使得矢量图像在许多情况下并不具备足够的优势。对于游戏开发而言,矢量图形虽然可以在以后进行缩放,但因为其渲染效率低,可能会导致游戏画面看起来更差,并且在性能上无法达到预期效果。
补充:关于 32 位拆分为两个独立进程的问题,《星球大战:旧共和国》确实这么做了
讨论中提到的技术似乎涉及将32位的系统拆分成两个独立的进程,这种做法听起来很奇怪。一般来说,这种技术可能是为了扩展内存访问范围,使得一个地址窗口能够访问更多的内存。然而,这种做法的效果和实现方式并不清晰,听起来并不是一个理想的选择。相反,可能更合适的做法是通过智能的流式处理来管理内存,而不是通过复杂的地址扩展来解决问题。虽然不确定是否存在更好的理由支持这种技术,但从目前的理解来看,这种做法似乎并不具备明显的优势。
仓库:
https://gitee.com/mrxiao_com/2d_game