回顾并制定今天的计划
最近几天,我们有一些偏离了原计划的方向,主要是开始了一些调试代码的工作。最初我们计划进行一些调试功能的添加,但是随着工作的深入,我们开始清理和整理调试界面的呈现方式,以便能够做一些更复杂、更精美的功能展示。
前几天,我们对全局变量进行了一些清理,并将状态进行了整合。今天,我们打算继续这个方向,进一步完善用户界面(UI),为调试和其他操作添加一些交互功能。我们已经为这个目标奠定了一些基础,所以今天我们计划推动这些功能的开发,争取实现一些按钮,使得我们能够快速地进行操作,比如开关性能分析器(Profiler)等。
目前,我们还没有完全确定最终的交互设计方式,因此我们将继续尝试,看看最终会达成什么效果。我有一些想法,甚至在考虑一些可能有些过于荒诞的方式去实现这些功能,但有时候正是这些看似荒诞的想法,可能会带来最好的解决方案。
总之,我们现在正在推进这些工作,探索不同的方式,看看哪些方法最适合我们实现目标。
试试一些极其荒唐的东西α
回顾一下代码,我们之前做了一些关于性能分析窗口的工作,主要是让我们能够控制性能分析窗口的大小。这是一个开始,涉及到了矩形的使用和一些概念化的工作,整体进展顺利。接下来的目标是继续推进这个方向,努力进一步优化和扩展当前的实现。
我们希望能够打开和关闭调试界面的不同部分
现在的目标是创建一些概念,类似于"控件"或者"组件",这些控件是我们需要显示的不同部分,可以通过某种控制方式进行开关操作。例如,通过点击某个按钮或者使用菜单来控制这些调试代码的显示与隐藏。当我们不需要它们时,它们不会干扰我们,我们可以随时关闭它们。当需要时,又可以重新打开。这是我希望实现的目标,虽然非常简单,但对提升调试的灵活性和易用性非常有帮助。
昨天我们还确保了循环实时代码编辑功能可以与调试系统兼容,应该不会出现问题。我还需要检查实时代码编辑是否与调试系统完美配合,结果显示一切正常,这对我们来说是个好消息。这样的话,我们就能更方便地调试那些难以复现的bug。假如这些bug能够进入循环状态,我们还可以查看性能数据,也许甚至可以冻结这些数据进行深入分析,虽然我不确定这是否会完全可行。
总的来说,当前的重点是使调试功能更加灵活,方便随时根据需求打开或关闭调试项。
目前,实时循环代码编辑会覆盖我们的输入,因此会阻止与调试 UI 的交互
关于如何实现这个目标,我们面临一个有趣的两难问题。如果我们把循环实时代码编辑功能整合得更好,就会出现一个矛盾的情况------录制功能会记录所有输入,这样一来我们就无法与调试代码进行任何交互了。这种情况下会显得有些复杂,因此我们需要考虑如何解决这个问题。但在目前这个阶段,我只是想确保我们实现的循环插件代码能够与调试系统兼容,结果看起来一切正常。
所以,现在我们应该继续进行接下来的工作,尝试实现更多的功能,推动项目向前发展。
使用鼠标右键作为"离合"键
我想尝试将右键鼠标按钮设计成一种"离合器"(clutch)的功能,通常在UI设计中,这个词是指按下一个按钮后,进入一种模式,能够访问许多菜单和选项。我的设想是,游戏在运行时一切正常,但当我按下右键时,会弹出一个UI界面,让我可以快速选择内容。这个界面可以是一个放射状排列的菜单,里面有多个选项,允许我快速打开或关闭一些功能。
具体的操作方式是,右键按下时,UI界面会弹出来,用户可以从中选择;如果按住右键不放,可能会进入一个快速开关的状态,进行一些快速的操作。我希望能实现一个类似的功能。
接下来,我将开始在调试代码中进行这个功能的实现。
打开和关闭性能分析器
首先,我打算从一个非常简单的功能开始,主要是让我们能够控制是否启用性能分析(profile)。我并不想在一开始就过于复杂化,因此我添加了一个布尔变量,用来表示性能分析是否开启,即是否显示相关内容。
接着,我查看了处理覆盖内容的代码,并且注意到与性能分析相关的部分。于是,我决定将这部分代码用一个条件语句包裹起来,让它仅在性能分析开启时才执行。具体做法是,将这部分内容放入一个"如果性能分析开启"的判断中。这样,运行游戏时,如果性能分析没有开启,相关内容就不会显示出来。
接下来,我们的目标是添加一个UI元素,使得用户能够通过交互来切换这个布尔变量的状态,也就是开启或关闭性能分析功能。这是一个非常基本的功能,我需要收集一些关于交互的信息,例如如何检测用户的操作等。

从自己熟悉的地方开始编写代码
在编写代码时,通常会采取从已知的部分开始,然后逐步向未知的部分推进的方法。这种方法帮助将问题分解成更容易处理的部分,而不是从一开始就试图解决所有的问题。具体来说,如果面对一个复杂的任务(比如开发一个径向菜单),不必一开始就考虑所有细节或者画出复杂的图表或者设计文档,尤其是当这些内容并没有提供实际帮助时。相反,应该从自己最熟悉的部分入手。
例如,首先可以专注于那些已经知道如何实现的功能,像是如何绘制图形。这些已知的部分能够快速实现,也能确保第一步是稳妥的。然后,逐步推进到自己不确定或者不熟悉的部分。通过这种方式,从已知到未知的过渡变得更为自然和可控。
这不仅是一种思维方式,也是一种具体的编程方法。它的关键在于避免过早地陷入过多不必要的抽象和设计,更多地是从实际的实现出发,逐步解决问题。在解决一个复杂问题时,逐步推进和从已知着手,可以避免陷入困境,并更清晰地理解解决方案的每个步骤。
总之,这种从已知到未知的逐步推进方法,可以帮助有效地处理复杂问题,并将开发过程变得更加清晰和可管理。
绘制一个径向菜单
在绘制径向菜单时,首先考虑的是实现菜单的基础绘制功能,而不关注界面设计或复杂的用户交互。开始时,可以创建一个简单的函数,比如 DrawRadialMenu
,并将其用作一个草稿平台。在这个函数中,不需要考虑太多具体细节,例如函数名或其是否应该作为一个独立的功能,而是专注于实现基本的绘制操作。
首先,需要一个用于渲染的对象(比如 render_group
)来处理绘制任务。同时,也可以考虑添加调试状态(debug state
),以便在后续开发中能够方便地进行调试和测试。在这个初始阶段,并不需要过于关注 UI 的实现,只是简单地绘制一个菜单的框架。
此时,不要提前设定菜单的样式或布局,而是将菜单假设为总是显示在屏幕上的状态,暂时忽略其他界面元素。这样做的目的是从最基本的绘制开始,逐步向复杂的交互和界面设计发展。
通过这种方法,可以专注于基本的实现细节,不受过多复杂设计的限制。首先完成基础的菜单绘制后,再逐步改进和完善其他功能,直到最终达成预期的效果。
假设我们有一些菜单项
首先,需要考虑的是创建菜单项。对于菜单项的数量和具体内容,初期不需要过多思考,只需假设有一些菜单项可供选择。可以随便列出一些可能的菜单项,例如:切换个人资料、切换输出、切换帧率计数器、标记循环点、绘制实体边界、绘制世界块边界等。虽然这些菜单项是否需要在实际应用中实现还不确定,但在当前阶段,只需要有一些基本的菜单项作为参考。
一旦有了菜单项,就可以开始思考如何绘制这些菜单项。为了绘制菜单项,显然需要遍历所有菜单项。这是一个理所当然的步骤,因此需要为每个菜单项设置一个索引。通过这个索引,可以逐个处理菜单项的绘制和展示,从而实现一个基本的菜单界面。
此时,关键是将注意力集中在菜单项的创建和显示上,而不需要立即关心其具体的功能或交互。这种方法帮助在实现过程中先建立一个基础框架,之后再逐步添加更多的功能和细节。
绘制菜单项
在绘制菜单项时,需要获取每个菜单项的文本(例如菜单项名称或文本)。一旦有了菜单项的文本,就要决定在哪里显示这些文本。可以利用已有的显示文本功能,例如通过 debug text out at
方法,将文本显示在指定的屏幕位置。
接下来,首先需要确定显示文本的位置。假设我们正在创建一个径向菜单,菜单项围绕一个中心点排列,因此需要计算每个菜单项相应的位置。为了做到这一点,需要明确文本的位置坐标,通常是一个点(x, y)坐标。这些坐标将决定每个菜单项文本在屏幕上的位置。
通过这些步骤,文本能够正确地显示在界面上的指定位置,接着就可以继续处理如何根据位置将文本显示在菜单的适当地方,确保菜单的布局符合预期。
(黑板讲解)将文本字符串围绕圆形排列
在实现径向菜单时,首先需要确定菜单项的位置,这些位置沿着圆形排列。为了做到这一点,首先需要定义菜单的半径(即圆的半径),并且通过一个参数化的值来控制这个半径的大小。接着,需要知道菜单项的数量,这样才能将圆形均匀地分割成对应数量的部分。
例如,如果有 8 个菜单项,那么整个圆周就会被分成 8 个等分,每个分段的角度就是 τ 8 \frac{\tau}{8} 8τ,其中 τ \tau τ 是圆的总角度(即 360°)。这个角度将用来确定每个菜单项的位置。通过计算每个菜单项的角度,可以利用三角函数来获取它们在圆上的坐标:使用余弦函数计算 x 坐标,使用正弦函数计算 y 坐标,得到的坐标就是圆上的某一点。然后,将这些坐标乘以半径,得到对应的外部圆上实际的菜单项位置。
得到这些坐标后,下一步就是在这些位置绘制文本。但是,为了让菜单看起来更美观,我们还需要将文本居中。当前的方法是将文本从坐标点的基准位置开始绘制,但这可能导致文本显示不够对齐,菜单看起来会有些歪斜。不过,这个问题在初步实现时并不严重,稍后可以针对文本的居中问题进行修正。
为了实现这个过程,可以先简单地绘制文本并在界面上显示菜单项。在这之前,可以设置一个函数来计算菜单项的位置,以便能够在正确的位置上显示每个菜单项的文本。
实现 Arm2
为了方便计算和绘制径向菜单项的位置,可以创建一个名为 arm_two
的函数,这个函数接收一个角度作为输入,并返回一个二维向量。这个向量的 x 坐标是该角度的余弦值,y 坐标是该角度的正弦值。这个函数的作用是根据角度计算一个单位向量,并且可以通过该向量表示圆周上某一点的位置。
在实际应用中,如果知道菜单项的数量,可以根据数量将整个圆周分成若干等份,然后通过计算每个菜单项的角度来确定它们的位置。假设菜单有 n 个项,那么每个菜单项的角度间隔就是 2 π n \frac{2\pi}{n} n2π,根据菜单项的顺序,逐步计算当前菜单项的角度。
一旦知道了角度,就可以通过 arm_two
函数计算出相应的单位向量,然后再根据菜单的半径将单位向量放大,得到实际的屏幕坐标。这个半径可以是一个可调参数,比如 200 像素,控制菜单的大小。将半径与计算出的单位向量相乘,就能得到最终的位置,确保菜单项的文本能够正确显示在屏幕上。
这个过程非常简单,首先计算角度,然后利用 arm_two
函数得到单位向量,最后根据菜单的半径放大单位向量,得出文本应该显示的位置。通过这些步骤,就可以准确地将文本放置在菜单项的位置上,从而完成菜单项的布局。


进行测试,看起来不错,但标签是否居中?
现在已经实现了径向菜单的基本功能,但目前菜单的位置似乎没有完全居中。虽然看起来可能已经居中了,但从视觉上来看,仍然感觉有些偏差,可能是因为计算或者绘制的位置存在某些细微问题。这个问题可能与菜单项的文本对齐有关,或者是菜单项的起始位置没有完全与圆心对齐,导致看起来不太准确。
因此,尽管菜单已经显示出来,但仍需要进一步检查和调整,以确保菜单项能够正确居中显示,尤其是文本的对齐方式。
标记圆心
为了更好地理解和调试径向菜单的显示,决定先绘制一个圆形,这样可以帮助检查菜单项的位置是否正确。为了清晰地看到圆的范围,使用了 push rect
来绘制一个矩形区域,目的是明确圆的实际边界。
首先,定义了一个矩形区域,这个矩形区域的中心点与圆心对齐,并且给定了一定的宽度和高度(例如两像素或三像素)。这个矩形框架会帮助更直观地看到圆形的位置。颜色设置为白色,确保在屏幕上能够明显看到这个矩形框。
通过这种方式,能够确认圆形的确是按预期绘制的。虽然当圆形位于基准线时可能不太容易直观判断,但一旦绘制了矩形框,就可以清晰地看到圆形的位置和大小。这个步骤帮助确认了圆形的位置是正确的,并且菜单项应该围绕这个圆形进行布局。
接下来,可能需要解决一些细节问题,尤其是在显示和排列菜单项时,进一步调整和优化圆形的显示效果。


使文本居中
为了能够更好地处理文本的布局,决定为文本添加一个矩形区域,这样可以方便地获取文本的尺寸,并且使文本能够围绕某个点居中显示。首先,计划利用现有的 debug text out
函数,进行一些扩展,让它能够支持获取文本的矩形区域。这是因为,文本的显示不仅仅是将文本直接输出,还需要考虑其边界和尺寸,以便更精确地定位和排列文本。
接下来,考虑在输出每个文本字符时,同时计算文本的矩形区域。具体来说,应该在渲染每个字符时,将它的坐标和尺寸记录下来,最终得出整个文本的边界矩形。这就需要修改现有的渲染代码,在输出每个字形时,同时计算并更新这个矩形区域。
通过这种方式,文本就能够根据矩形的边界进行居中调整,使得文本能够准确地在预定的位置显示,而不会偏离预期的布局。
计算标签尺寸的代码与绘制标签的代码有许多相似之处。我们需要确保这些操作的实现不会失去同步
为了避免文本绘制代码和矩形计算代码不同步,决定将文本输出部分抽象成一个新的函数,称为 DebugTextOp
。这个新函数将允许根据不同的操作类型(比如绘制文本或者计算文本的尺寸)来决定要执行的操作。
具体来说,DebugTextOp
函数会接收文本相关的参数,例如字体信息等,但不会直接进行文本的绘制操作,而是根据操作码(例如 draw text
或 calculate text size
)来执行不同的任务。如果操作是绘制文本,函数将执行绘制操作;如果是计算文本尺寸,则它将计算并返回文本的矩形区域。
在文本渲染过程中,特别是在绘制位图时,需要知道位图的尺寸,以便正确计算文本的边界。这时,通过在 PushBitmap
调用中,获取实际位图的尺寸,就能得到准确的矩形区域,从而确保文本在界面中的位置和布局是正确的。
通过这种方式,可以更灵活地处理文本的绘制和尺寸计算,同时避免代码的重复和不一致性。

将位图尺寸计算从 PushBitmap
中提取出来,以便其他函数可以调用
为了避免在不同地方重复调用 PushBitmap
,决定将获取位图尺寸和对齐信息的代码提取为一个独立的函数。这个新函数将返回位图的尺寸、对齐方式和其他相关信息,而不再需要直接调用 PushBitmap
。通过这种方式,可以更加灵活地获取位图的相关信息,并在其他地方使用这些信息,而不需要重复执行绘制操作。
新的函数可能会被命名为类似 GetTextDimension
,它会接收所需的参数并返回位图的尺寸、对齐信息以及其他相关的计算数据。这样,代码就能在不直接绘制的情况下,通过返回的数据来了解位图的尺寸和位置。该函数会处理计算位图尺寸、对齐等相关逻辑,并返回所有必要的信息,如位图的大小和对齐基准。
为了确保代码的一致性和避免出错,所有计算位图尺寸和对齐的逻辑都会被统一管理。将这些逻辑从原来的代码中提取出来之后,可以通过新的函数来访问这些信息,确保代码路径的一致性,避免因为重复代码的不同步而产生问题。
通过这种方法,能够更高效、更灵活地处理位图的尺寸和位置,同时保持代码的清晰和一致。


定义 DebugTextOp
为了使文本处理更灵活,决定在代码中引入一个"文本操作"机制。在此机制中,首先定义一个 debug_text_op
,用于指定操作的类型,如"绘制文本"或"获取文本尺寸"等。这样,可以将操作作为参数传入,从而动态决定对文本的处理方式。
具体来说,可以引入一个类似 DebugTextOp
的函数,它接受一个操作类型参数以及其他必要的参数,然后根据传入的操作类型执行相应的逻辑。例如,若操作类型为"绘制文本",则执行文本的绘制操作;若操作类型为"获取文本尺寸",则执行获取文本尺寸的逻辑。
通过这种方式,可以统一管理文本相关的处理逻辑,并且根据不同的需求灵活地选择操作类型,从而提高代码的可扩展性和可维护性。同时,避免了重复的代码,使得文本处理更加简洁和高效。

PushBitmap
将忽略未加载的位图
为了能够正确地获取和处理位图,首先需要从 bitmap ID
获取实际的已加载位图。由于这个过程可能会失败,因此在尝试获取位图时,必须考虑到位图可能不存在的情况。如果无法加载位图,可以简单地跳过这部分文本处理,因为该位图不会在当前帧被渲染。
实现这个逻辑时,可以先尝试通过 GetBitmap
获取位图的尺寸。如果获取失败,则不处理该文本,因为该资产不会在当前帧渲染。这种方式简化了流程,避免了对不存在的位图进行多余的处理。
同时,为了使代码更加简洁和清晰,可以考虑对现有的代码进行一定的整理和清理,确保各个部分更容易理解和维护。通过传递必要的参数,如 debug state
、DebugState、
FontInfo和
String`,可以确保正确执行文本的绘制或尺寸获取操作。

在 DEBUGStart
时预先加载调试字体
在代码优化过程中,首先需要清理一些不必要的步骤,避免每次都重复相同的操作。特别是在调试状态处理时,确保在进行调试时不必重复执行每次渲染的逻辑。当处理渲染分组时,可以直接处理字体信息,避免每次都重新传递和加载。
具体来说,可以在开始渲染时(比如调用 BeginRender
)时,确定渲染分组已经有效,并且字体信息也已经加载。这时,我们只需将字体信息推送并获取,确保渲染过程中可以直接使用相关数据,而无需每次都重复此操作。
对于调试操作(DebugTextOp
),可以根据是否有有效的字体信息来决定是否继续执行。在没有字体信息的情况下,调试操作可以自动跳过相关步骤,而不需要再次传递这些数据。
进一步的优化方案是,将字体检查和加载步骤进行条件判断,只有在字体信息存在时,才会继续执行渲染操作。若条件满足,则可以顺利进入渲染过程,不需要重复进行设置和处理。这样可以减少不必要的重复代码,提升效率,并确保渲染逻辑更加简洁和清晰。
总体目标是通过改进调试和渲染的流程,使得每次渲染时不需要重复加载和处理相同的资源,只在必要时进行检查和更新,从而简化代码并提高运行效率。


修复一些编译错误
通过优化后,代码不再需要每次都检查和加载字体信息,避免了重复的操作。现在,只要确保字体已加载并且有有效的字体信息,就可以直接进行渲染操作,这样能大大简化逻辑,减少不必要的资源加载。
在具体的实现中,传递给调试状态的字体信息已经不再需要每次都传递,代码变得更加简洁。我们移除了原本不必要的字体信息传递,仅保留了加载字体和字体信息的相关操作。通过这种方式,代码更清晰,也符合个人的偏好,将字体信息的检查放在了前面。
在完成代码修改后,还对其进行了检查,确保没有破坏原有功能,所有操作和调试逻辑都正常运行,确保代码状态良好。最终,通过这些改进,不仅提高了代码的效率,也使得代码的可维护性和清晰度得到了提升。

通过计算每个字符的矩形并取其并集来计算字符串的尺寸
首先,通过获取字体的位图尺寸,可以轻松地获取所需的矩形区域,这个矩形区域描述了字体的空间大小。具体来说,位图尺寸(bitmap dim
)将包含所有需要的信息,如变换后的尺寸、对齐方式等。这些信息可以帮助我们确定字体在原始空间中的位置和大小。
在获取这些信息时,主要关注的是变换后的尺寸和位置(P),这两个信息会告诉我们字体的边界。在计算时,可以使用这两个信息来生成一个矩形,它将表示字形在输入空间中的边界。
接下来,可以通过创建一个初始的矩形,设定为无限大,以便在后续过程中逐步更新该矩形。每次处理字形时,都可以将当前的矩形与新的字形矩形进行联合,最终得到一个包含所有字形的完整矩形。
具体的实现中,矩形的计算是通过不断地进行"联合"(Union
)操作完成的。每次将当前字形的矩形与已有矩形进行联合,逐渐扩大矩形范围,直到最终覆盖所有字形的区域。
在实际代码中,通过这种方式,我们能够确保获取到包含所有字形的最小矩形区域。并且,由于这是一个不断更新的过程,因此可以确保最终得到的矩形是准确的,涵盖了所有需要的字形区域。

实现 rectangle2
版本的"倒置无限矩形"
在处理矩形时,需要一个特殊的矩形,即"反无限矩形",它能够在初始化时覆盖整个空间。为了方便每次操作时都能使用这个矩形,决定为所有矩形创建一个类似的"反无限矩形"版本。这样,每次处理新的字形矩形时,可以用这个矩形作为初始值进行合并。
首先,创建一个新的矩形类型(例如rectangle - version
)并进行相应的初始化。通过将该矩形与现有的矩形进行联合(Union
)操作,逐渐扩大最终的矩形范围。这样可以确保每次都能正确地处理字形矩形,最后得到一个包含所有字形的最小矩形区域。
接着,发现有一些矩形类型(如v2
)缺少必要的转换方法,需要为其提供适当的转换功能。例如,缺少将v3
类型转换为v2
类型的转换函数。这个缺失显得有些疏忽,因为转换两个不同版本的矩形应该是一个常见的需求,解决此问题后可以更方便地处理各种矩形数据。
此外,对于template
的使用,考虑到有些问题可以通过模板来解决,模板在这种情况下确实能够简化代码,因为它允许在不同类型之间共享相同的操作,避免了重复的代码实现。通过这种方式,可以更高效地管理不同类型的矩形和相关操作。
总的来说,通过不断改进矩形处理的代码,确保了矩形的合并和转换操作更加灵活和简洁,避免了重复的逻辑,并为不同类型的矩形提供了必要的支持。



测试新代码,实现 DEBUGGetTextSize
经过调整,代码现在看起来应该是正常的。接下来,可以使用一个新的函数来验证这些修改是否有效,确保做出的改动没有引入问题。首先,检查文本输出是否正常,并确保通过调试功能计算文本大小。
为了实现这一点,设计了一个新的函数 DEBUGGetTextSize
,该函数的输入参数为文本内容(字符串)。由于不再需要传递额外的位置信息(如P
),所以可以简化调用,只需要提供字符串作为输入。然后,调用 DebugTextOp
函数,并将计算结果存储在result
中。为了简化调试,文本的位置设定为 (0, 0)
,这样可以确保在原点位置输出文本。
最后,不要忘记将调试状态(debug state
)传递给该函数,这样就能够确保调试信息正确记录并处理。这一系列的修改应该能够帮助确认程序是否按预期工作,避免引入任何不必要的复杂性。
绘制文本标签的边界以检查其位置
通过这一系列的改进,现在可以尝试绘制文本的边界并检查其准确性。为了做到这一点,首先需要创建一个矩形(rectangle
),它将表示文本的边界框。然后,使用之前的字符串来计算这个矩形的尺寸,确保文本的边界能够正确显示。
接下来,可以使用 push rect
函数来绘制矩形边界,暂时使用普通的矩形绘制方法(而不是边框绘制),因为当前还没有实现专门的矩形边框绘制功能。为了更好地查看文本位置,可以使用一种深蓝色的背景色,将其围绕文本绘制,方便进行视觉验证,确认文本是否位于正确的位置。
绘制矩形时,确保使用正确的文本位置。为了做到这一点,矩形需要根据文本的起始位置(P
)进行偏移调整。如果没有现成的偏移功能,可以检查是否已有类似的功能,虽然目前仅在rectangle3
类型中找到了偏移函数,但rectangle2
类型没有提供对应的重载。因此,可以考虑增加一个合适的偏移函数,将矩形根据文本的实际位置进行调整。
最终,通过这些操作,能够确保文本的显示和边界框正确无误,位置也精确无误。这种方法确保了文本的尺寸和边界准确性,从而可以有效验证文本布局的正确性。



标签的位置是精确的
通过这一步,文本的边界框成功绘制出来并且能够进行准确的验证。首先,创建了一个矩形来表示文本的边界框,并使用之前获取的字符串来计算文本的尺寸。这确保了能够正确地绘制出文本所占的空间。
为了验证文本的位置和大小,绘制了一个矩形,使用深蓝色背景将文本框围住,从而便于查看和确认文本是否放置在正确的位置。这样,通过视觉上确认边界框的位置和大小,能够确保文本的布局和位置准确无误。
此外,考虑到文本的位置需要进行偏移调整,使用了一个偏移函数来确保矩形的位置与文本的位置相符。尽管目前偏移函数只适用于某些类型的矩形,但通过适当的调整和添加,确保了矩形的正确位置。
最终,所有操作的结果证明是有效的,文本的边界框显示正常,且文本位置和尺寸都符合预期。
关于"操作"代码转换的一些想法
这段代码实现了一个有效的转换方法,允许在相同的代码块中生成不同的结果,特别是在不太关注性能的情况下(比如调试代码时)。这种方法通过将操作作为参数传递给函数,避免了重复编写类似的代码。这样做的好处是可以灵活地改变操作的内容,而不需要写两份几乎相同的代码。
这种方式也是一种绕过C语言的限制的解决方案,C语言不像某些功能更强大的语言(比如函数式编程语言)那样,提供了处理闭包等高级功能的机制。由于C和C++语言在设计上并未考虑到这些需求,因此开发者往往需要找到替代方案。尽管C语言的设计有其缺点,但通过将操作码作为参数传递的方式,仍然能够解决问题并且在实践中表现得更好。
总的来说,这种方法是通过简单的操作传递和参数化来实现灵活的功能扩展,而不是依赖于语言本身的高级特性。这使得代码保持简洁且可维护,同时能够应对多种场景。
使标签居中
目标是将文本居中,这个过程变得相对简单。现在已经能够根据已知的文本位置(TextP
)和文本的边界(bounds
)来轻松实现文本的居中。方法是首先获取文本边界的尺寸,然后将其一半的宽度从当前位置中减去,从而使文本居中。
通过这种方式,文本准确地围绕着目标点居中显示。原本绘制文本的边界框位置并不再需要,因此可以去掉这部分代码,使得实现更加简洁。现在,文本和图形都能够正确显示,并且菜单也正确地围绕中心点进行布局。
为了验证效果,可以测试多个菜单项,确保它们的显示位置正确。这一步骤验证了文本的居中和布局的准确性。虽然当前的实现已经满足基本要求,但可能还需要进一步调整布局,特别是对于顶部的菜单项,因为顶部的文本可能会遇到比底部更复杂的布局问题。因此,未来可能需要对文本的排列方式做一些进一步的优化。
总的来说,现在的代码已经能够有效地确保文本的居中,并且菜单的布局看起来正确,虽然还可能需要考虑更多的细节来优化显示。



找出最接近鼠标指针的菜单索引,以更改其颜色
为了实现菜单项的选择功能,我们可以利用鼠标位置来判断用户当前选择了哪个菜单项。首先,可以通过鼠标的位置和菜单项的位置计算距离,找到与鼠标位置最接近的菜单项。这一过程相对简单,只需要维护一个最短距离,并在遍历所有菜单项时更新它。
具体来说,首先初始化一个最佳菜单索引和最佳距离,然后通过计算鼠标位置与每个菜单项的位置的距离,找出距离最小的菜单项。可以使用距离的平方来避免计算开方,从而提高效率。每当遇到一个距离比当前最佳距离更小的菜单项时,就更新最佳菜单索引。
在找到鼠标当前选择的菜单项后,最后通过该索引来确定所选菜单项。在这过程中,我们还可以通过颜色来区分当前选中的菜单项。例如,可以将选中的菜单项的颜色改为黄色,而其他的保持默认颜色。这样用户就能够清晰地看到他们正在选择哪个菜单项。
此外,对于文本的颜色,可以添加一个颜色参数,允许在绘制文本时指定颜色。默认情况下,颜色为白色,但如果当前菜单项是用户鼠标悬停的目标,就可以改变颜色,突出显示选中的菜单项。
通过这种方式,可以很容易地实现一个带有选择功能的菜单,用户只需要根据鼠标位置即可快速选择菜单项。这个过程不仅简单,而且能够提供直观的交互效果。


激活菜单项
在结束当前的之前,可能需要先处理一些细节。为了确保所有功能都按预期工作,可以先将一些必要的内容处理完。比如,可以考虑是否需要询问前置条件或者其他相关的设置,并将这些内容暂时添加到当前的代码中,确保代码结构更加完善和清晰。
这一过程的重点是确保在继续开发之前,所有必须的前置条件已经正确设置好,并且代码中的每个部分都能正确地交互。通过整理和检查这些细节,可以减少之后开发过程中可能出现的问题。
这样做类似于 β
在处理这些功能时,首先需要处理鼠标点击事件。当用户按下鼠标按钮时,我们就能执行相应的菜单选项。可以设定某些条件,比如按下鼠标按钮时,检查菜单项并做出反应。此时,我们可以通过鼠标位置来确定用户选择的菜单项。
接着,检查鼠标按钮的状态。如果按钮已经释放,意味着用户已经完成了选择。此时,我们会处理他们选择的菜单项,执行相应的操作。这需要检查鼠标当前位置,确定用户点击了哪个选项。如果选择的选项与当前状态相关联(比如切换某个功能开关),那么就执行相关操作。
此外,可能需要处理一个"调试菜单"的显示逻辑,确保在需要的时候能够显示出来,并且根据用户的鼠标操作进行交互。在鼠标点击事件发生后,还需要更新一些状态信息,比如是否切换了配置或功能。这些操作都相对简单,核心是根据鼠标输入更新状态并执行相应的功能。
最终,通过这些步骤,可以实现用户交互逻辑,确保调试菜单能够根据鼠标操作正确反应并执行预期功能。

进行测试
此时,径向菜单已经开始运作,虽然还有一些不希望出现的情况需要调整。例如,在菜单显示的位置,当前是始终从屏幕中心开始显示,而不是鼠标点击的地方。因此,决定加入一个逻辑来根据鼠标按下的地方来调整菜单的显示位置,而不是默认的居中显示。
另外,还需要考虑调试过程中的操作,例如切换调试功能的按钮,执行某些调试操作时的暂停和状态切换,这些都需要通过鼠标事件进行控制。最终目标是让菜单在点击右键时弹出,并能正确响应用户的操作。


将径向菜单放置在鼠标点击的位置
在调试过程中,想要实现菜单的正确显示位置,特别是在鼠标点击时能够根据点击的位置动态移动菜单。首先,通过检查Input->MouseButtons[PlatformMouseButton_Right].HalfTransitionCount > 0
是否大于零,来判断是否点击了鼠标。当鼠标按钮按下时,记录下当时的鼠标位置,即"菜单位置(MenuP)"。然后,在处理文本的显示位置时,将菜单位置偏移到鼠标按下的位置,这样就能让菜单随着鼠标的移动而调整位置,从而模拟一个正常的径向菜单行为。
完成这个调整后,菜单就能够根据鼠标位置来显示,并且可以实现功能切换,比如"切换性能图"或"切换调试数据收集"。此时,虽然径向菜单已经可以正常工作,但界面还没有很漂亮,需要进一步优化。




预告未来的内容
目前的UI仍然需要大量改进,才能达到理想的状态。当前的菜单项显得有些零散,布局上不够紧密,功能虽然已经实现,但仍处于基础阶段,仅仅是实现了基本的使用代码。接下来的工作重点是将这些内容整理得更加连贯,使其结构更加合理,并提升整体的可用性和美观度。
在后续的优化过程中,将采用典型的"压缩导向编程"方法,对已经实现的功能进行提炼和优化,提高代码的组织性,使菜单更加清晰易用。在接下来的开发中,会进一步调整和完善径向菜单的表现形式,使其真正达到良好的用户体验。虽然目前只是完成了基础功能,但径向菜单的引入已经是一个不错的开始,未来会围绕这一系统继续优化和扩展功能。
我想你为 v3
到 v2
的转换添加了 v3.xy
在转换过程中,之前的实现方式实际上更加智能。这样可以更好地控制所获取的内容,确保选择的准确性和灵活性。相比之下,当前的方法可能没有之前的方法高效,因此更早的决策在这一点上显得更加合理。这样的方式能够更精准地决定需要提取的内容,提高代码的可维护性和可读性。
即使圆形菜单变得混乱,我们也可以让每个按钮指向另一个圆形菜单 / 层级 / 子集
即使圆形菜单中的选项变得混乱,也可以通过让每个按钮指向另一层子菜单的方式来优化布局。这样可以将选项分层组织,使菜单更易于使用。目前尚未完全确定最终的菜单布局方式,需要进一步观察有哪些选项需要包含在其中。
接下来的计划是深入研究如何合理安排这些选项,使其既直观又高效。未来会在实践过程中逐步探索合适的设计方案,并调整菜单的交互方式,以确保其灵活性和可用性。
你能更详细地解释 C 语言中的闭包以及如何实现吗?我不是很理解
在 C 语言中,无法直接实现像其他高级语言那样的闭包(Closure)机制,因此我们采用了一种"简化版"的方法来实现类似的功能。
我们面临的需求是,希望能够在某些复杂的迭代过程中,将代码的某个部分作为可替换的逻辑块,同时让它能够访问特定的数据。例如,在遍历世界块(world chunk)时,我们需要对每个块执行特定的操作,这部分逻辑应当是可变的。同样,在绘制矩形时,可能需要根据不同的着色方式来计算像素颜色,而不改变外围的迭代和计算逻辑。
在更高级的语言中,闭包允许代码块携带相关的变量环境,使其可以在不同的上下文中执行,而仍然能够访问原始数据。但 C 语言不具备这样的能力,所以我们需要另一种方式来实现类似的效果。
我们的方法是,在关键代码块中,使用一个变量来标识当前应执行的具体逻辑,而不是动态地传递函数或代码块。例如,在执行某些着色逻辑时,我们在代码内部插入了一个变量,根据该变量的值来决定执行哪种计算逻辑。这种方式避免了 C++ 中 lambda 表达式带来的复杂性,同时利用分支判断的开销相对较小,能够在性能和灵活性之间取得平衡。
虽然这种方法不能完全模拟闭包,例如它无法让不同代码块拥有各自独立的数据环境,也无法直接访问调用者的栈帧,但在实际使用中,它仍然是一种可行的解决方案。通过这种方式,我们能够在 C 语言中实现类似于闭包的功能,提升代码的可复用性和灵活性,同时避免引入过于复杂的语言特性。
在 C 语言中模拟闭包的示例
我们希望在不使用 C++ 的 lambda
或 std::function
,也不使用函数指针的情况下,在 C 语言里实现类似闭包的行为。
示例 1:使用 switch
变量模拟闭包逻辑
假设我们有一个绘制函数,需要在不同的模式下执行不同的着色逻辑,但外围的遍历逻辑保持不变。
c
#include <stdio.h>
// 定义几种不同的着色模式
typedef enum {
MODE_NORMAL,
MODE_HIGHLIGHT,
MODE_SHADOW
} ShaderMode;
// 全局变量(或者局部静态变量)存储当前的着色模式
ShaderMode currentMode = MODE_NORMAL;
// 处理像素的函数
void processPixel(int x, int y) {
int color = 0;
// 使用变量控制代码逻辑
switch (currentMode) {
case MODE_NORMAL:
color = 255; // 正常模式
break;
case MODE_HIGHLIGHT:
color = 128; // 高亮模式
break;
case MODE_SHADOW:
color = 64; // 阴影模式
break;
}
printf("Pixel at (%d, %d) -> Color: %d\n", x, y, color);
}
// 遍历所有像素的函数
void drawRectangle(int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
processPixel(x, y);
}
}
}
int main() {
// 先使用普通模式绘制
printf("Normal mode:\n");
currentMode = MODE_NORMAL;
drawRectangle(3, 2);
// 切换到高亮模式
printf("\nHighlight mode:\n");
currentMode = MODE_HIGHLIGHT;
drawRectangle(3, 2);
// 切换到阴影模式
printf("\nShadow mode:\n");
currentMode = MODE_SHADOW;
drawRectangle(3, 2);
return 0;
}
解释:
currentMode
充当一个"闭包变量",控制processPixel
该如何计算颜色。drawRectangle
只负责遍历像素,它不关心具体的绘制逻辑。- 通过修改
currentMode
,我们可以改变processPixel
的行为,而不需要修改drawRectangle
。
示例 2:使用 struct
模拟闭包(携带数据的代码块)
如果我们需要在不同的模式下,使用不同的参数(比如亮度、对比度等),可以用 struct
来封装数据,使得"闭包"能够携带环境变量。
c
#include <stdio.h>
// 定义一个闭包数据结构
typedef struct {
int brightness;
int contrast;
} ShaderContext;
// 处理像素的函数,接收一个环境变量
void processPixel(int x, int y, ShaderContext *ctx) {
int color = (255 * ctx->brightness) / 100;
color = color * ctx->contrast / 100;
printf("Pixel at (%d, %d) -> Color: %d\n", x, y, color);
}
// 遍历所有像素的函数
void drawRectangle(int width, int height, ShaderContext *ctx) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
processPixel(x, y, ctx);
}
}
}
int main() {
// 定义不同的环境变量
ShaderContext normal = {100, 100}; // 正常亮度和对比度
ShaderContext highlight = {150, 120}; // 高亮模式
ShaderContext shadow = {50, 80}; // 阴影模式
// 传递不同的闭包数据,执行相同的遍历逻辑
printf("Normal mode:\n");
drawRectangle(3, 2, &normal);
printf("\nHighlight mode:\n");
drawRectangle(3, 2, &highlight);
printf("\nShadow mode:\n");
drawRectangle(3, 2, &shadow);
return 0;
}
解释:
ShaderContext
结构体充当"闭包",它携带了brightness
和contrast
两个参数,影响processPixel
的计算方式。drawRectangle
不需要知道具体的着色逻辑,它只是简单地调用processPixel
并传入ShaderContext
,实现了逻辑解耦。
总结
-
第一种方法(全局变量 +
switch
)- 适用于简单场景,只需要修改单一逻辑,而不需要额外参数。
- 缺点是
currentMode
变量是全局的,不适用于多线程或需要多个独立实例的情况。
-
第二种方法(
struct
传递环境变量)- 适用于更复杂的场景,比如不同的参数配置,或者在不同的地方同时应用不同的逻辑。
ShaderContext
允许processPixel
访问额外的上下文信息,类似于闭包中的"绑定变量"。
在 C 语言中,这种方法虽然不像真正的闭包那样灵活,但可以有效实现类似的功能,同时避免 C++ 复杂的 lambda
或 std::function
机制带来的额外开销。
你为什么选择径向菜单而不是列表或其他格式?
我们选择径向菜单(Radial Menu)而不是列表(List)或其他布局方式的原因主要是其快速选择的特性。
-
快速启用和关闭选项:
- 径向菜单适用于快速操作,用户可以通过鼠标或手柄的方向选择需要的选项,减少鼠标移动的距离,提高效率。
- 适用于开关类选项,比如"启用/禁用调试模式"等。
-
直观的方向选择:
- 由于菜单项是围绕中心点分布,用户可以用方向性思维快速定位所需选项,而不像列表那样需要上下滚动寻找。
- 适用于数量适中的选项(通常 6-8 个),不会占据过多屏幕空间。
-
减少视觉干扰:
- 列表或下拉菜单可能会占据较大面积,影响游戏界面的可视性,而径向菜单在短暂显示后可以快速消失,不干扰主要内容。
其他补充
- 不仅限于径向菜单 :
- 也会加入列表菜单,因为有些情况下列表比径向菜单更适合,特别是当选项较多、需要精确选择时,比如详细的设置或高级选项。
- 可能会结合两种方式,径向菜单用于快速选择常见功能,而列表菜单提供更精细的控制。
总的来说,径向菜单适用于快速操作 ,而列表适用于更复杂或精细的选择,两者各有优劣,我们会在不同场景中使用合适的菜单类型。
如果菜单项与圆形边界对齐,那样文本碰撞的可能性会更小
我们计划对径向菜单进行一些优化,使其更直观和美观,同时减少文本重叠的问题。
优化方向
-
菜单项对齐圆形边界:
- 让菜单选项沿着圆的边缘排列,而不是随意分布,这样可以确保每个选项都均匀分布,减少视觉混乱。
- 这样做还能让玩家更容易判断每个选项的位置,提升交互体验。
-
优化文本显示,避免重叠:
- 目前如果选项过多,文本可能会发生重叠,导致阅读困难。
- 解决方案 :
- 自动换行:当文本过长时,调整字号或进行换行,使其在有限空间内显示完整信息。
- 缩略文本:如果菜单空间有限,可以在 hover 或选中时显示完整文本,避免影响整体布局。
- 弧形文本对齐:让文本沿着径向菜单的曲线排列,使整体布局更加自然。
-
整体视觉调整:
- 增加一些 UI 细节,比如半透明背景、柔和阴影、悬停高亮等,让菜单更加现代化。
- 确保选项不会因过多的 UI 细节影响可读性。
这些改动可以让径向菜单既实用又美观,同时确保操作的流畅性和直观性。
你提到 C 语言不支持闭包。你是否尝试过通过"仿函数"或其他方法来"黑科技"实现它?
我们并没有真正尝试在 C 语言中实现闭包(closures),也没有用函子(functors)等方式去模拟它。不过,在一些情况下,我们确实会使用多重管理栈(multiple managed stacks)来达到类似闭包的效果。例如,在一些代码实现中,我们会创建多个受控的内存管理区域,并在其中存储数据,这在某种程度上涉及到了闭包的概念。
尽管我们没有真正"破解"C 语言的闭包问题,但在实践中,某些方法可以部分实现类似闭包的行为。例如,我们可以使用结构体(struct)来封装函数指针及其所需的上下文数据,并将这个结构体传递给其他函数,从而实现类似闭包的作用。不过,这种方法仍然需要手动管理内存和作用域,与真正的闭包相比要复杂得多。
总体而言,我们并没有深入尝试在 C 语言中模拟闭包,而是更倾向于使用已有的技术手段来实现类似的功能。
你是否考虑过在直播中添加一个调试输出,以显示你的按键?
我们并没有考虑过在调试输出中显示按键记录,比如在屏幕底部显示按下的组合键(例如按下 Ctrl + G
时在屏幕底部显示 Ctrl + G
)。主要原因是我们并不特别关注是否要向观众展示具体的按键操作,也不认为这是当前工作的重点。
此外,我们并不是 Emacs 的狂热用户,因此并不特别在意观众是否能从操作中学习到如何使用 Emacs。我们的主要关注点仍然是功能实现和代码逻辑,而不是特定工具的使用教学。当然,如果未来有更好的可视化方式来辅助调试和开发,我们也可以考虑相应的改进方案。
预测 的编辑器 4coder
未来可能会考虑使用更好的编辑器,或许在某个编辑器(如 Fourth-Dimension)完善并适应代码需求之后,我们可以直接切换到那个环境。不过目前仍然在使用现有的工具,没有特别着急去更换编辑器的计划。
当然,如果有更高效、更适合当前开发需求的编辑器出现,并且能够满足我们的工作流,我们会考虑尝试并逐步过渡到新的工具,以提升开发效率和使用体验。
经常在使用他人的 API / 引擎时,会不得不"与之斗争"才能实现自己想要的功能。你认为这是否是一个信号,表明应该放弃该 API,尝试另一个,或者自己编写一个,而不是一直"斗争"?
使用他人的 API 或引擎时,常常会面临与其不兼容或不符合需求的情况,这时是否应该换一个 API 或自己编写代码,这个问题是一个非常复杂的决策,答案因情况而异。
通常情况下,使用像 Unity 这样的引擎时,可能会经常遇到与引擎本身的架构不匹配的情况。在这种情况下,你会做出一个权衡决策,即虽然 Unity 不能完美满足需求,但它提供了大量的现成代码,能帮助你节省开发时间。相比自己从头开始编写代码,尽管可能会遇到一些挑战或问题,使用 Unity 仍然可能是一个更好的选择,特别是对于经验较少的开发者来说。所以,有时候与其"战斗",也许就意味着接受这类牺牲,节省其他时间来进行开发。
然而,若是 API 造成的困扰非常大,导致需要不断的工作去弥补其不足,甚至你最终不得不重写大部分代码,这时就需要认真考虑是否还要继续使用该 API。在这种情况下,继续使用 API 可能会适得其反,浪费更多时间。
对于一个新手游戏开发者来说,这种决策非常难做。因为没有经历过所有工具的使用,很难判断哪个是最适合的工具。像 Unreal Engine、Unity、Game Maker 等,都可能在不同的项目中面临一些"不适合"的问题。因此,选择一个合适的工具并不容易,开发者只能通过多次尝试和实践,才能理解各种工具的优劣和适用范围。
当开发者与 API 进行"斗争"时,这通常意味着该 API 无法以期望的方式工作,尽管如此,并不一定意味着应该放弃。更重要的是要意识到,每个决定背后都有成本和收益的权衡。如果最终判断自己编写某个组件或功能从头开始更为合适,那就应当去做。然而,这种判断往往需要积累一定的经验,因为对一个新手来说,很难清楚地判断在一个 API 下战斗是否值得,或者写自己的代码是否更高效。
另外,在某些情况下,问题并不完全在 API 上,而是开发者对 API 的理解不足。比如,当年不懂某些 API 时,可能会觉得它们设计得很糟糕或者难用,但随着对编程知识和多线程等概念的理解加深,开发者可能会发现这些 API 实际上是非常优秀的。因此,很多时候,API 的设计问题并不是设计本身有问题,而是开发者未能充分理解它的工作原理。
总的来说,选择是否换掉 API、使用其他工具或自己动手写代码,都是基于经验和项目需求的综合判断。
由于 draw_rectangle_quickly
代码位于单独的翻译单元中,你会用纯汇编编写它,而不是 C / 内联汇编吗?
讨论了是否将某些代码从C内建函数(intrinsics)转换为其他方式实现。提到,虽然将代码转为其他方式是一项有趣的练习,但在游戏发布之前并不会去做这样的改动。计划是尽量保持当前的实现方式,确保游戏顺利发布,而不是在此阶段进行复杂的重构。
你能详细讲解一下 I/O 完成端口(IOCP)吗?你刚才提到它了
讨论了I/O完成端口(I/O Completion Port)API,提到这个话题需要花费较长时间进行详细讲解,因此决定将其留到另一天再讨论。因为时间有限,无法在当前的讨论中深入解释为何I/O完成端口API是一个好的API。