游戏引擎学习第200天

回顾并为当天的工作设定阶段目标

今天的工作方向是集中在用户界面(UI)代码的实现,虽然这个项目本身并不涉及大量的UI设计,但我们会暂时偏离原来的任务,进行一个小小的探索。主要是因为在之前的工作中,UI部分并没有太多涉及,而这对于那些希望了解如何在自己的项目中实现UI代码的人来说,可能会有帮助。虽然这个项目本身不是一个以UI为主的游戏,比如像模拟城市、文明或者模拟人生那样有大量的UI界面,但我们仍然可以花点时间来讨论UI的实现,特别是如何实现调试UI,这对于游戏开发中调试和监控来说是非常重要的。

目标是让UI的实现更加简洁和灵活,以便以后可以快速增加新功能和界面,而不会给程序员带来过多的负担。在调试UI的实现上,重点是让它尽可能的简便和高效,这样即便后续需要扩展,也能通过简单的代码修改和调整来实现新的功能,而不需要大规模重构。

在昨天的工作中,我们已经抽象了交互代码,这让我们能够更好地管理和复用这些代码。接下来的工作是让UI绘制部分更加顺畅,确保调试UI的绘制能够正确显示。今天,我们会重点关注调试UI的绘制实现。这个任务可以分为两个部分:一部分是继续改善UI绘制的方式,另一部分是关于如何处理对象的深拷贝问题。当前我们有一个问题,就是当复制一个对象(如渲染器)时,它只是做了引用复制,导致多个对象间的数据完全同步,这样可能会产生不希望的效果,我们需要解决这个问题。

不过,考虑到时间安排,我们决定暂时专注于调试UI的绘制部分,确保它能够正常工作,等这个部分完成后再考虑其他改进措施。因此,今天的任务主要是让绘制部分的代码运行起来,并确保它符合我们的期望。

查看我们当前如何绘制内容 (game.debug.cpp)

今天的目标是改进目前的绘制代码,避免它显得过于临时和零散。当前的绘制方式是通过一个简单的 for 循环,遍历所有需要绘制的类型,并在循环中直接编写绘制代码。虽然这种方式在早期阶段能快速实现需求,但随着功能的增加,代码变得越来越杂乱,维护和扩展变得更加困难。

最初在处理交互时,虽然我们也使用了类似的方法,但之后我们创建了一个 debug interaction 类型来封装交互的内容。这样做的好处是可以将交互逻辑抽象出来,避免重复代码,同时也方便了对交互状态(例如高亮)进行管理。通过这种方式,代码变得更加整洁、清晰,也提高了灵活性和可维护性。

接下来,我们计划采用类似的方式重构绘制代码。目前的绘制代码也比较零散,涉及到计算各个元素的位置、大小等操作,并且这些操作的实现较为分散。为了避免重复和冗余的代码,我们打算将这些绘制逻辑抽取到一个共享的地方,这样可以统一管理并保证代码的整洁性,同时也便于后续的修改和扩展。

通过这种重构,绘制代码将变得更加模块化,可以复用的部分被提取出来,代码的可读性和可维护性也会大大提高。此外,这种改进也能让我们在添加新的绘制元素时,不需要重复编写相似的代码,而是可以直接调用已经封装好的方法,保持代码的简洁性和一致性。

引入布局结构体以包含局部变量 (game_debug.cpp)

今天的工作重点是将现有的重复代码提取出来,进行简化和重构,以便更高效地管理和修改。在当前的代码中,有多个地方在处理类似的功能,例如绘制位图和计数器线程列表等。这些功能大多数情况下都从相同的位置开始,而这些位置通常都是相同的,然而目前的代码在不同地方重复了相似的内容。举个例子,如果我们想要修改这些内容的缩进方式,必须在多个地方进行修改,这样就暴露了代码重复的问题。

为了避免这种冗余和潜在的维护问题,我们决定将这些重复的部分提取出来,创建一个共同的结构体来管理这些变量。具体来说,我们从当前的代码中提取了深度(depth)、行进量(line advance)以及相关的坐标等变量,将它们放入一个结构体中。通过这种方式,所有需要使用这些变量的函数,都可以通过传递该结构体来访问这些数据。

这意味着,我们不仅仅是将这些变量放入结构体中,而是希望通过这种方式将它们封装起来,使得这些数据能方便地传递给其他函数。这样做的好处是,原先的变量分散在不同的地方,现在我们只需要通过结构体传递一次,就可以让函数知道这些数据的状态。通过这种方式,我们实现了类似闭包(closure)一样的效果,使得代码更加模块化和简洁。

总之,今天的工作是将一些重复的逻辑抽取出来,封装成结构体,这样不仅避免了代码重复,还为以后可能的修改和扩展提供了便利。这种做法让后续的功能扩展和修改变得更加灵活和高效。

引入 Advance

目前,我们正在优化和整理现有的绘制逻辑,使代码更具条理性,同时减少冗余部分。当前的代码存在一些问题,比如在处理边界(bounds)时有些不必要的复杂性。这种复杂性主要来源于我们在尝试复用代码路径,以确保推进逻辑(advancement code)的一致性。然而,这种方式并不必要,因为实际上没有理由强制所有对象都使用相同的边界推进方式,每个对象甚至可能有多个边界(bounds)。

因此,我们的目标是重新组织代码,使边界的处理更加直观。首先,我们将边界的计算逻辑移入局部作用域,确保它们在局部函数内部处理,而不是分散在整个代码块中。这样,我们可以减少边界处理逻辑的分散性,使代码更易维护。

在文本绘制方面,之前的实现方式较为麻烦,因为文本的边界需要特定处理,而这种处理方式导致代码在多个地方做了额外的适配工作。现在,我们正在调整代码结构,使这些适配工作更自然地融入整个系统,不再需要额外的"弯曲"代码逻辑来适配不同的情况。

接下来,我们引入了一种新的推进方式,即创建一个 Advance 函数,并将其应用到布局(layout)对象上。这个 Advance 函数的核心思想是让对象自己负责边界推进,而不是在外部显式地计算和更新 Y 坐标。具体来说,我们在 Advance 函数中接收 layoutbounds,然后直接更新 layout 的 Y 坐标,使其等于 bounds 的最小角(min corner)。这样,我们在主代码逻辑中不再需要手动逐步更新坐标,而是只需调用 Advance 函数即可完成相应的推进操作。

随着 Advance 函数的引入,我们在代码的各个地方逐步替换了原本的手动推进逻辑,使代码更加清晰、模块化。例如,所有涉及 layout 相关的变量,如 layout.addxlayout.atylayout.spacingXlayout.spacingYlayout.depth 以及 layout.lineAdvance,现在都可以通过 layout 结构体进行集中管理,而不需要在各处分别维护。这种方式减少了重复代码,提高了可维护性,并为未来可能的修改和扩展提供了便利。

在代码迁移的过程中,我们逐步处理了编译错误,确保所有相关变量都正确引用 layout 结构体。这样一来,代码逻辑更加清晰,绘制与布局推进的逻辑分离,使得后续的开发更加灵活和高效。

整体而言,我们的工作重点是简化边界计算逻辑、引入 Advance 机制,并将所有相关变量集中到 layout 结构体中进行管理。这样不仅减少了代码重复,还使得布局推进的逻辑更加直观和易于维护,从而优化整个 UI 系统的开发流程。

运行游戏并发现它和之前一样

目前,我们已经完成了对代码的重构,使其在逻辑上保持与之前相同的功能,同时优化了代码的结构和可读性。从目前的运行结果来看,代码的行为与之前保持一致,这表明我们的调整是合理的,未引入额外的错误或影响原有功能的变动。

具体来说,我们引入了一种新的 Advance 机制,使得 layout 结构体可以更自然地管理和推进布局状态,避免了之前代码中手动计算和更新坐标的繁琐操作。通过将 layout.addxlayout.atylayout.spacingXlayout.spacingYlayout.depthlayout.lineAdvance 等变量集中管理,我们减少了代码冗余,提高了可维护性。

此外,我们调整了边界(bounds)的计算方式,使其局部化,避免了边界计算逻辑在代码各处分散的情况。现在,边界的计算逻辑封装在 Advance 函数内部,确保了每个对象能够独立管理自己的边界,而不是依赖全局代码进行推进。这种改进不仅减少了代码重复,还使得逻辑更加直观。

目前,所有的调整已经完成,并且测试结果表明代码功能仍然保持一致,这说明我们的改进没有破坏原有的功能。接下来,我们可以继续优化代码,使其更具扩展性和可读性。

引入 PlaceRectangle (game_debug.cpp)

现在,我们已经有了 layout 结构体,并且所有相关的代码都开始使用它。接下来的目标是进一步优化代码,提取出明显重复的部分,使其更加模块化和可复用。

在检查 counter thread list 相关的代码时,我们发现其中的 dimension(尺寸) 计算逻辑是手写的,但实际上这个计算逻辑是通用的,并不需要每个地方都单独实现。因此,我们可以将其提取出来,使任何具有 dimension 属性的对象都能够复用这一计算方法。

改进方案

我们计划实现一个 PlaceRectangle 函数,它的作用是根据 dimension 的信息,返回适合绘制的 矩形区域(rectangle) 。这样,我们就可以避免手动计算 minCornermaxCorner 的重复操作,使代码更加简洁和模块化。

  1. 原始代码的问题

    • 代码中多次手动计算 minCornermaxCorner,然后再基于它们创建 rectangle,导致重复逻辑。
    • 这些计算完全依赖于 layout 的信息,如 layout.atXlayout.atY,没有必要在多个地方重新计算。
  2. 新的 PlaceRectangle 设计

    • PlaceRectangle 函数接收 dimension 作为参数,并结合 layout 的当前状态,自动计算矩形的位置。
    • 该函数会返回 minCornermaxCorner,提供完整的矩形定义。
    • 这样,调用者无需关心具体的计算细节,而是直接得到结果,提高了代码的可读性和复用性。

代码优化过程

  • 我们在 layout 结构体中增加 PlaceRectangle 方法,使其能够根据传入的 dimension 自动计算 minCornermaxCorner
  • 计算逻辑保持与之前相同,但被封装在一个函数中,以便复用。
  • 代码中所有手动计算 minCornermaxCorner 的地方,现在可以直接调用 PlaceRectangle,避免重复代码,提高代码整洁度。
  • PlaceRectangle 方法的返回值是 rectangle(minCorner, maxCorner),这样我们可以直接使用它,而不需要手动计算每个角的坐标。

最终优化效果

  • 减少代码重复 :所有的 minCornermaxCorner 计算逻辑被封装在 PlaceRectangle 中,不需要每次手动写这些计算。
  • 提高代码可读性 :现在代码逻辑更直观,调用 PlaceRectangle 即可获得正确的矩形区域,而不需要手动计算边界。
  • 增强可维护性 :如果以后需要修改 rectangle 的计算方式,只需要修改 PlaceRectangle,而不需要到每个调用位置进行修改。

通过这些优化,我们成功消除了大量冗余代码,使 layout 结构更加清晰和易用,同时提升了整个代码的模块化程度。

运行游戏并发现它正常工作

理论上,现在所有的 性能分析(profile) 相关代码都已经经过优化后的 layout 处理,并且应该可以正常工作。经过测试,结果符合预期,所有功能都如预期运行,没有出现问题,这证明了优化方案的可行性。

这意味着,我们成功地将 绘制布局的逻辑 进行了提取和封装,使得 layout 结构能够更高效地管理界面元素的 尺寸计算、对齐方式以及位置确定 。同时,所有涉及 性能分析 的界面绘制代码现在都使用了这一改进后的 layout,避免了手动计算 边界(bounds),大幅减少了冗余代码,提高了可维护性和可读性。

这是优化的第一步,接下来可以考虑进一步改进,使得 layout 机制更加通用和灵活。

使用 PlaceRectangle 计算元素的布局

接下来,我们的 第二步优化 目标是复用刚刚提取的 layout 代码,使其适用于 位图(bitmap) 的布局计算,从而减少代码重复,提高整体一致性。

当前在 位图缩放(bitmap scale) 计算过程中,我们进行了一些 尺寸修正(dimension correction) ,比如 bitmap_display_dimx 之类的计算。然而,这些计算并不真正依赖 位图的具体位置(Min corner) ,而是仅仅需要确定位图的 尺寸(size) 。换句话说,我们之前传递 Min corner 其实是多余的,因为最终的布局计算才决定了它的实际位置。

因此,我们可以在 尺寸修正 之后,直接调用 layout 相关代码,让 layout 机制帮我们完成 位图的布局计算 。这样可以减少重复的逻辑,让所有布局相关的计算保持一致,并确保代码更加 简洁、可维护且易于扩展

在这一优化过程中,我们还需要 正确处理 size P(位图尺寸) ,并使用 layout 计算出来的 bounds(边界) 来确定 位图最终的位置 。这样,绘制位图时就可以直接使用 GetMinCorner(bounds),而不再需要手动计算位置。

这样优化后,我们的 位图布局 逻辑将会更加规范化,并与其他界面元素的布局计算保持一致,使整个代码结构更加清晰合理。

运行游戏并显示使用相同布局的元素

我们成功压缩了代码,这是一项很好的改进。现在,无论是 计数器线程列表(counter thread list) 还是 位图(bitmap) 的布局计算,都已经使用了相同的 layout 例程(layout routine)。这正是我们期望的结果。

通过这样的优化,任何需要类似布局功能的代码,都可以直接调用这一 layout 机制 ,自动完成 边界计算(bounds calculation),而不需要再手动计算具体位置。这不仅减少了代码重复,还大大降低了维护成本,使整个代码结构更加清晰、易用、可扩展。

接下来,我们希望进一步优化和调整代码,使其更加通用化和易用化。

考虑提取 SizeP 计算的难度 (game_debug.cpp)

我们希望进一步扩展 layout 机制 ,使其可以自动处理 SizeBox(尺寸框) 。在之前的实现中,我们发现 计数器线程列表(counter thread list)位图(bitmap) 视图的 SizeBox 逻辑是相同的。因此,如果 layout 机制 能够统一处理 SizeBox,那么在以后添加类似的功能时,我们就能直接复用代码,而不需要手动实现这些细节。

目标

在一个 良好的 UI 设计 中,开发者应该能够 方便地 选择现有组件,而不需要关心底层的实现细节。因此,我们希望做到:

  1. 任何组件 只要需要 SizeBox ,都可以 直接声明,而不需要额外处理布局逻辑。
  2. 自动计算 SizeBox 的边界(bounds),确保布局系统能够正确放置它们。
  3. 减少代码重复 ,让 layout 机制 统一管理这些 SizeBox。

当前遇到的问题

  1. SizeBox 交互冲突

    • 在布局中,我们发现 SizeBox 可能会与 bitmap 组件的交互区域发生冲突。
    • 例如,当我们尝试拖动 SizeBox 时,系统可能会 误认为 我们正在操作 bitmap,从而触发错误的交互行为。
  2. Hit Testing(点击检测)问题

    • 目前,我们的 点击检测 (hit test)是按顺序执行的,先检测到的对象优先级较低 ,而 后检测到的对象优先级较高
    • 这意味着,如果 bitmap 的 hit test 发生在 SizeBox 之前,那么 SizeBox 的交互可能会被覆盖。
  3. 可能的解决方案

    • 引入 Z 轴深度(Z-value) :通过给不同的 UI 组件分配 不同的 Z 轴层级,让更高层级的组件(如 SizeBox)可以正确拦截交互事件。
    • 优化交互检测机制 :改进 hit test 逻辑,确保 鼠标指针位于 SizeBox 内部时,优先触发 SizeBox 的交互,而不是被底层的 bitmap 误捕获。
    • 调整架构 :确保 layout 机制 在计算 SizeBox 的边界 时,同时考虑 交互优先级,避免错误的 UI 行为。

下一步计划

  • 优化 layout 机制,使其可以自动计算 SizeBox 的布局信息
  • 调整 hit testing 逻辑,确保 SizeBox 能正确拦截交互事件
  • 测试新的布局机制,观察是否能解决 UI 交互问题

简化并剥离 DEBUGDrawMainMenu 中的计算 (game_debug.cpp)

我们正在尝试优化 布局系统(layout system) ,让其更加模块化、简洁,并减少代码重复。我们的目标是创建一个 通用的布局组件(layout element),能够自动处理大小调整(sizable)、交互(interaction)等行为,从而简化 UI 代码。


核心思路

  1. 定义通用的布局元素(element)

    • 通过 begin_element(Rectangle, dimensions) 创建一个矩形布局元素。
    • 通过 EndElement() 结束该元素,并返回是否处理了鼠标交互。
  2. 支持可调整大小(sizable)

    • 通过 、MakeElementSizable() 让当前元素支持尺寸调整。
    • 这样,任何 UI 组件只需调用这一方法,就能具备尺寸调整能力,而无需额外逻辑。
  3. 优化交互处理(interaction handling)

    • EndElement() 返回一个布尔值,指示当前元素是否捕获了鼠标交互。
    • 这样可以避免引入 Z 轴排序(Z-value sorting) 这类复杂的机制,同时保证交互处理的正确性。

实现步骤

  1. 改进布局结构

    • 目前的代码中,布局信息(如尺寸、边界)需要在 元素结束后(EndElement) 才能确定。
    • 我们调整逻辑,使其能够在元素创建时就传递尺寸地址,确保后续计算能正确使用这些尺寸信息。
  2. 消除多余代码

    • 通过新的 element 机制,可以消除大量 手动处理尺寸交互 的代码。
    • 例如,原本需要手动计算 SizeBoxinteraction,现在这些都可以由 layout 机制自动管理
  3. 优化文本布局

    • 现在可以使用 begin_element() 直接传递文本尺寸(text_bounds.widthlayout_line_advance),避免手动计算宽高。
    • 交互逻辑也可以通过 默认交互参数(default_interaction) 直接传递,进一步减少代码量。

当前优化成果

  • 代码结构更清晰
    • 现在的 UI 代码更紧凑,只需要调用 begin_element()EndElement(),其余逻辑都交给 layout 机制 处理。
  • 减少代码重复
    • 之前手动计算 SizeBoxinteractionlayout_spacing 等的代码已被移除,全部由 layout 组件 统一管理。
  • 交互逻辑更可靠
    • 通过 EndElement() 返回的交互信息,我们避免了 错误的鼠标捕获,无需额外的 Z 轴管理。

下一步计划

  1. 测试新布局系统的兼容性 ,确保所有 UI 组件都能正确使用 begin_element()EndElement()
  2. 优化默认行为 ,例如是否需要显式传递 layout_spacing,还是让 layout 机制 自动管理这些值。
  3. 进一步精简代码 ,尝试将 文本渲染 也纳入 element 系统,以减少 UI 代码的复杂度。

引入 BeginElementRectangle、MakeElementSizable、DefaultInteraction 和 EndElement (game_debug.cpp)

  • 函数概览

    • BeginElementRectangle:返回一个布局元素,接收布局对象和指向矩形尺寸的指针。
    • 使元素可调整大小:、MakeElementSizable 通过添加 size_box 使元素可调整大小。
    • 设置默认交互方式:DefaultInteraction 将默认交互方式设为调试交互。
    • 结束元素定义:EndElement 标志着元素定义完成。
  • 核心逻辑

    目前所有逻辑都可以内联处理,无需推迟到 EndElement 调用后再执行。这种方式更简洁直观。

    • 变量 V2 dim 代表尺寸,并作为指针传入。
    • layout 变量存储布局信息。
    • sizeinteractionbounds 都初始化为空值,以便后续计算。
  • 计算元素的总尺寸

    • TotalDim 初始值为用户指定的 dim
    • 若元素可调整大小,则在 TotalDim 基础上扩展一个额外区域(即 size_box)。
    • size_box 的尺寸可设定为固定值,如 4px
  • 计算矩形的边界

    • TotalMinCornerTotalMaxCorner 代表整个元素的最小/最大角点。
    • InteriorMinCornerInteriorMaxCorner 代表元素的内部矩形区域。
    • element_bounds 存储的是返回给用户的最终可用区域。
  • 计算元素的交互区域

    • size_box 仅作用于矩形右下角,允许用户拖动调整大小。
    • 计算 size_box 位置时,InteriorMaxCorner 作为起点,TotalMaxCorner 作为终点。
  • 渲染边框

    • 通过绘制四条边框矩形来实现,分别是左边、右边、顶部和底部。
    • left_edgeright_edge 通过 TotalMinCornerInteriorMinCorner 定义。
    • top_edgebottom_edge 通过 TotalMinCornerInteriorMaxCorner 计算。
  • 修正调试状态传递

    • debug_state 直接存入 layout,避免每次传递参数时重复操作。
    • MouseP 也放入 layout,确保所有函数都能正确获取鼠标位置。
  • 优化代码

    • 移除 add_radius_to,直接对 dim 进行加法运算,提高简洁性。
    • getWidth 相关调用修正为 GetDim xGetDim y,确保计算正确性。
    • push_rect 统一使用 ElementBounds 作为绘制区域。

整体来看,这部分代码主要完成了 UI 元素的初始化、尺寸调整、边界计算、交互区域定义以及边框绘制,并进行了部分优化以提高可读性和执行效率。

运行游戏并发现错误

我们目前实现了几个关键的 UI 相关函数,其中包括 begin_element_rectangle,它接受一个布局(layout)和指向矩形尺寸的指针,返回一个布局元素。此外,我们还实现了 make_element_sizable,用于添加可调整大小的功能,即为元素添加尺寸框(SizeBox)。同时,我们提供了 set_default_interaction 作为默认交互方式,并通过 EndElement 来结束元素的创建,表示该 UI 元素的所有属性已定义完成。

实现细节

我们决定将所有逻辑内联实现,而不是推迟到 EndElement 调用时再进行整合。这样可以在调用 EndElement 时直接构造 UI 元素,并确保所有计算在此时完成。

begin_element_rectangle 里:

  • layout 直接赋值。
  • dim 赋值为传入的尺寸指针。
  • size 指针初始化为空。
  • interaction 设为默认交互方式(null)。
  • bounds 设为 null

EndElement 被调用时:

  1. 计算元素的总尺寸(total dim),如果该元素是可调整大小的,则需要在原始尺寸的基础上增加一个尺寸框(SizeBox)。
  2. 计算总边界(total bounds),包括 UI 组件的整体区域。
  3. 计算内部边界(interior bounds),即实际内容区域,不包括尺寸框。
  4. 计算 minCornermaxCorner,分别表示总边界和内部边界的最小、最大角坐标。
  5. 计算尺寸框的矩形区域,即 SizeBox 位置。

对于尺寸框(SizeBox):

  • 计算它的具体位置,使其出现在元素的右下角。
  • 确保其交互逻辑能够正确处理用户输入(如鼠标拖动等)。

EndElement 里,还需要处理 mouseP(鼠标位置):

  • 这需要从 layout 结构中获取,并确保它能够被正确传递到 UI 组件中。
  • 这样可以确保 SizeBox 可以正确响应鼠标交互。

绘制边框

为了更清晰地展示 UI 组件,我们决定绘制元素边框:

  1. 左边框 :从 total minCornerinterior minCorner
  2. 右边框 :从 interior maxCornertotal maxCorner
  3. 上边框 :从 total minCorner Yinterior minCorner Y,在 X 方向上覆盖整个宽度。
  4. 下边框 :从 interior maxCorner Ytotal maxCorner Y,同样覆盖整个宽度。

此外,我们还计划在 SizeBox 处绘制一个额外的交互区域,使其更加清晰可见,并便于用户操作。

调试和优化

  • 修正 get_widthget_dim_y 等函数的调用,以匹配实际的数据结构。
  • 通过 debugState 传递 UI 组件的调试信息,并优化其访问方式,以减少多次传递。
  • 代码中可能存在一些小错误,例如 add_radius_to 计算不准确,我们优化了这部分逻辑,使其直接在 dim 上进行简单的加法运算,而不使用 add_radius_to

当前问题

  • 可能在 SizeBox 计算时没有正确添加条件检查,导致某些情况下 SizeBox 被错误绘制。
  • 需要进一步测试 EndElement 内部的交互逻辑,确保尺寸调整功能能正确响应用户输入。

接下来,我们将进行调试,查看最终的 UI 渲染效果,看看是否还有逻辑问题需要修正。

仅当元素具有大小时才推送矩形,并停止让文本元素变得可调整大小 (game_debug.cpp)

在实现 UI 元素的过程中,我们遇到了一些问题,并进行了修正和优化。

问题分析与修正

我们发现,所有的 UI 计算(如边界计算、尺寸调整等)只有在元素具有 SizeBox 时才会执行。这意味着,如果某个元素本身并未被标记为可调整大小(sizable),那么所有这些计算都不应该发生。因此,我们需要确保:

  1. 只有在元素具有 SizeBox 时,才执行相关的计算和操作。
  2. 避免不必要的计算,以提高性能和代码的清晰度。

之前的问题在于,某些计算没有被正确地限制在 SizeBox 存在的情况下执行,导致了一些意外的行为。因此,我们需要确保:

  • 如果 SizeBox 存在,则计算边界、尺寸和 SizeBox 位置。
  • 如果 SizeBox 不存在,则跳过这些计算,以避免错误。

错误定位

在排查过程中,我们发现了几个关键错误:

  1. 错误地将某些 UI 计算应用到了没有 SizeBox 的元素上

    • 这导致了一些无意义的计算,甚至可能影响其他 UI 元素的布局。
    • 需要确保 SizeBox 相关代码只运行在 sizable 的元素上。
  2. 未正确生成 UI 元素

    • element size 计算时,我们需要正确地检测 SizeBox 是否存在,并据此决定是否推送矩形边界数据。
    • 之前的代码错误地执行了 push all the wrecks(推送所有矩形数据),即使元素本身并不支持尺寸调整,导致了一些意外行为。
  3. 逻辑错误:忘记正确标记元素为 sizable

    • 代码逻辑上,我们需要正确地在元素创建时指定 sizable 属性,否则即使代码流程是正确的,仍然不会触发尺寸调整功能。
    • 这一点属于操作失误(operator error),即手动设置时的疏忽。

修正方案

  • 修正 if 语句,确保 SizeBox 计算只在 sizable 元素上执行。
  • 优化 EndElement 逻辑,避免对非 sizable 元素执行不必要的计算。
  • 检查 element size 计算流程,确保它正确地推送矩形数据,仅在 sizable 元素存在时执行推送。
  • 在元素创建时正确设置 sizable,以避免后续计算出现错误。

后续优化

  • 增加调试输出,以便更快定位 SizeBox 相关的计算错误。
  • 进一步优化 EndElement 逻辑,减少不必要的计算步骤,提高运行效率。
  • 确保所有 UI 组件的 SizeBox 逻辑保持一致,以避免不同组件间出现不兼容问题。

接下来,我们将测试修改后的代码,确保 SizeBox 的计算和 UI 渲染逻辑正确无误。

添加 DefaultInteraction (game_debug.cpp)

在 UI 交互逻辑的实现过程中,我们现在需要添加默认的交互行为。

目标

  • 为元素添加默认交互行为。
  • 检测元素是否具有交互类型(interaction type),并根据该类型进行交互处理。
  • 如果元素交互区域(bounds)包含当前交互位置,则更新调试状态(debug state)。
  • 确保 SizeBox 在交互优先级上高于普通元素,即便它们不重叠,也要在交互优先级上优先处理 SizeBox

实现方案

  1. 检测元素是否具有交互类型

    • 通过 interaction.type 判断当前元素是否被赋予了交互类型。
    • 如果 interaction.type 具有特定值,说明该元素支持交互,需要进一步处理。
  2. 判断交互位置是否在元素的 bounds

    • 采用 IsInRectangle 方法,检测当前交互点是否落入元素的 bounds 范围内。
    • bounds 指的是元素的内部区域,而非整个 UI 组件。
  3. 更新调试状态

    • 如果 bounds 内检测成功,则将 debugStates.nextHotInteraction 设置为当前交互的元素。
    • 这样可以跟踪当前正在被交互的 UI 组件,便于调试和优化。
  4. 处理 SizeBox 交互优先级

    • 确保 SizeBox 交互优先级最高 ,即便 SizeBox 与普通元素没有重叠,也要优先处理它的交互逻辑。
    • 这样即使 SizeBox 和其他交互元素发生重叠,SizeBox 仍然能够正确响应交互事件。

问题与解决

  • 交互元素优先级管理

    需要确保 SizeBox 在交互处理时优先级更高。这样即便某个普通 UI 元素有交互,SizeBox 仍然能正确捕获交互事件。

  • 交互边界检测的优化

    • bounds 的检测逻辑需要保证足够精准,以避免误判交互区域。
    • 可以优化 IsInRectangle 逻辑,使其更高效地判断当前鼠标或触摸点是否落在 bounds 内部。
  • 调试工具的改进

    • debugStates.nextHotInteraction 需要正确更新,以便后续在调试工具中能够直观显示当前交互的元素。
    • 可以考虑加入额外的日志或 UI 提示,方便跟踪交互状态的变化。

后续优化方向

  • 优化 SizeBox 与普通交互元素的优先级管理,使其适用于更复杂的 UI 场景。
  • 改进 IsInRectangle 逻辑,提高交互检测的精确度和性能。
  • 增加可视化调试工具,帮助开发者快速定位交互问题。
  • 测试 SizeBox 在不同 UI 组件层级下的交互表现,确保它能够始终正确响应交互事件。

接下来,我们将进行测试,确保 SizeBox 交互的优先级正确,并观察 debugStates.nextHotInteraction 是否能准确反映当前交互状态。

运行游戏并看到一切正常工作

目前来看,一切都运行得相当顺利,比预期的效果要好很多,整个流程比预想中更顺畅,基本上没有遇到需要额外调试的问题,这点让人非常满意。

当前进展

  • 交互逻辑和 UI 处理工作已经顺利完成。
  • size box 组件能够正常工作,并且整体布局逻辑已经独立拆分,使得代码结构更加清晰。
  • 所有 UI 元素的交互行为都能正确响应,测试结果良好。

进一步的扩展

目前已经具备了足够的基础来进行扩展,可以考虑以下方向:

  1. 改进 size box 的可操作性

    • 目前 size box 只是简单的框架,但可以进一步优化,比如:
      • size box 拥有更丰富的操作方式,比如可以直接从不同的边或角进行调整大小。
      • 甚至可以将其变成一个厚边框(thick frame),允许用户从多个方向调整尺寸。
  2. 增加 UI 组件的通用属性

    • 现在创建新 UI 组件的代码量已经大幅减少,可以轻松扩展 UI 元素的能力
    • 例如,可以为 UI 组件添加:
      • 可移动(movable) 属性,使组件可以被拖拽。
      • 可调整(resizable) 属性,使组件大小可变。
      • 可旋转(rotatable) 属性,使组件支持旋转操作。
    • 通过这种方式,可以实现一个更加模块化和灵活的 UI 系统
  3. 优化布局逻辑

    • 目前所有的布局代码已经完全拆分,使得扩展新的 UI 组件变得非常简单。
    • 后续可以进一步优化,使得添加新 UI 组件时不需要修改已有的代码结构,只需增量添加新功能即可。

下一步计划

  • 暂缓进一步开发,留待明天继续。
  • 当前进度是一个很好的停顿点,因为基础架构已经搭建完成。
  • 明天可以专注于增加新功能,比如让 UI 元素更具互动性,并支持更多自定义操作。

整体来说,整个系统已经变得非常灵活,现在可以非常轻松地添加新的 UI 组件,并赋予它们新的交互属性,使其具备更丰富的功能。

相关推荐
云上艺旅14 小时前
K8S学习之基础七十四:部署在线书店bookinfo
学习·云原生·容器·kubernetes
你觉得20514 小时前
哈尔滨工业大学DeepSeek公开课:探索大模型原理、技术与应用从GPT到DeepSeek|附视频与讲义下载方法
大数据·人工智能·python·gpt·学习·机器学习·aigc
A旧城以西15 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
无所谓จุ๊บ15 小时前
VTK知识学习(50)- 交互与Widget(一)
学习·vtk
FAREWELL0007516 小时前
C#核心学习(七)面向对象--封装(6)C#中的拓展方法与运算符重载: 让代码更“聪明”的魔法
学习·c#·面向对象·运算符重载·oop·拓展方法
吴梓穆16 小时前
UE5学习笔记 FPS游戏制作38 继承标准UI
笔记·学习·ue5
Three~stone16 小时前
MySQL学习集--DDL
数据库·sql·学习
齐尹秦17 小时前
HTML 音频(Audio)学习笔记
学习
瞌睡不来17 小时前
(学习总结32)Linux 基础 IO
linux·学习·io
Moonnnn.17 小时前
运算放大器(四)滤波电路(滤波器)
笔记·学习·硬件工程