游戏引擎学习第191天

回顾并制定今天的计划

最近几天,我们有一些偏离了原计划的方向,主要是开始了一些调试代码的工作。最初我们计划进行一些调试功能的添加,但是随着工作的深入,我们开始清理和整理调试界面的呈现方式,以便能够做一些更复杂、更精美的功能展示。

前几天,我们对全局变量进行了一些清理,并将状态进行了整合。今天,我们打算继续这个方向,进一步完善用户界面(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 textcalculate text size)来执行不同的任务。如果操作是绘制文本,函数将执行绘制操作;如果是计算文本尺寸,则它将计算并返回文本的矩形区域。

在文本渲染过程中,特别是在绘制位图时,需要知道位图的尺寸,以便正确计算文本的边界。这时,通过在 PushBitmap 调用中,获取实际位图的尺寸,就能得到准确的矩形区域,从而确保文本在界面中的位置和布局是正确的。

通过这种方式,可以更灵活地处理文本的绘制和尺寸计算,同时避免代码的重复和不一致性。

将位图尺寸计算从 PushBitmap 中提取出来,以便其他函数可以调用

为了避免在不同地方重复调用 PushBitmap,决定将获取位图尺寸和对齐信息的代码提取为一个独立的函数。这个新函数将返回位图的尺寸、对齐方式和其他相关信息,而不再需要直接调用 PushBitmap。通过这种方式,可以更加灵活地获取位图的相关信息,并在其他地方使用这些信息,而不需要重复执行绘制操作。

新的函数可能会被命名为类似 GetTextDimension,它会接收所需的参数并返回位图的尺寸、对齐信息以及其他相关的计算数据。这样,代码就能在不直接绘制的情况下,通过返回的数据来了解位图的尺寸和位置。该函数会处理计算位图尺寸、对齐等相关逻辑,并返回所有必要的信息,如位图的大小和对齐基准。

为了确保代码的一致性和避免出错,所有计算位图尺寸和对齐的逻辑都会被统一管理。将这些逻辑从原来的代码中提取出来之后,可以通过新的函数来访问这些信息,确保代码路径的一致性,避免因为重复代码的不同步而产生问题。

通过这种方法,能够更高效、更灵活地处理位图的尺寸和位置,同时保持代码的清晰和一致。

定义 DebugTextOp

为了使文本处理更灵活,决定在代码中引入一个"文本操作"机制。在此机制中,首先定义一个 debug_text_op,用于指定操作的类型,如"绘制文本"或"获取文本尺寸"等。这样,可以将操作作为参数传入,从而动态决定对文本的处理方式。

具体来说,可以引入一个类似 DebugTextOp 的函数,它接受一个操作类型参数以及其他必要的参数,然后根据传入的操作类型执行相应的逻辑。例如,若操作类型为"绘制文本",则执行文本的绘制操作;若操作类型为"获取文本尺寸",则执行获取文本尺寸的逻辑。

通过这种方式,可以统一管理文本相关的处理逻辑,并且根据不同的需求灵活地选择操作类型,从而提高代码的可扩展性和可维护性。同时,避免了重复的代码,使得文本处理更加简洁和高效。

PushBitmap 将忽略未加载的位图

为了能够正确地获取和处理位图,首先需要从 bitmap ID 获取实际的已加载位图。由于这个过程可能会失败,因此在尝试获取位图时,必须考虑到位图可能不存在的情况。如果无法加载位图,可以简单地跳过这部分文本处理,因为该位图不会在当前帧被渲染。

实现这个逻辑时,可以先尝试通过 GetBitmap 获取位图的尺寸。如果获取失败,则不处理该文本,因为该资产不会在当前帧渲染。这种方式简化了流程,避免了对不存在的位图进行多余的处理。

同时,为了使代码更加简洁和清晰,可以考虑对现有的代码进行一定的整理和清理,确保各个部分更容易理解和维护。通过传递必要的参数,如 debug stateDebugState、FontInfoString`,可以确保正确执行文本的绘制或尺寸获取操作。

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仍然需要大量改进,才能达到理想的状态。当前的菜单项显得有些零散,布局上不够紧密,功能虽然已经实现,但仍处于基础阶段,仅仅是实现了基本的使用代码。接下来的工作重点是将这些内容整理得更加连贯,使其结构更加合理,并提升整体的可用性和美观度。

在后续的优化过程中,将采用典型的"压缩导向编程"方法,对已经实现的功能进行提炼和优化,提高代码的组织性,使菜单更加清晰易用。在接下来的开发中,会进一步调整和完善径向菜单的表现形式,使其真正达到良好的用户体验。虽然目前只是完成了基础功能,但径向菜单的引入已经是一个不错的开始,未来会围绕这一系统继续优化和扩展功能。

我想你为 v3v2 的转换添加了 v3.xy

在转换过程中,之前的实现方式实际上更加智能。这样可以更好地控制所获取的内容,确保选择的准确性和灵活性。相比之下,当前的方法可能没有之前的方法高效,因此更早的决策在这一点上显得更加合理。这样的方式能够更精准地决定需要提取的内容,提高代码的可维护性和可读性。

即使圆形菜单变得混乱,我们也可以让每个按钮指向另一个圆形菜单 / 层级 / 子集

即使圆形菜单中的选项变得混乱,也可以通过让每个按钮指向另一层子菜单的方式来优化布局。这样可以将选项分层组织,使菜单更易于使用。目前尚未完全确定最终的菜单布局方式,需要进一步观察有哪些选项需要包含在其中。

接下来的计划是深入研究如何合理安排这些选项,使其既直观又高效。未来会在实践过程中逐步探索合适的设计方案,并调整菜单的交互方式,以确保其灵活性和可用性。

你能更详细地解释 C 语言中的闭包以及如何实现吗?我不是很理解

在 C 语言中,无法直接实现像其他高级语言那样的闭包(Closure)机制,因此我们采用了一种"简化版"的方法来实现类似的功能。

我们面临的需求是,希望能够在某些复杂的迭代过程中,将代码的某个部分作为可替换的逻辑块,同时让它能够访问特定的数据。例如,在遍历世界块(world chunk)时,我们需要对每个块执行特定的操作,这部分逻辑应当是可变的。同样,在绘制矩形时,可能需要根据不同的着色方式来计算像素颜色,而不改变外围的迭代和计算逻辑。

在更高级的语言中,闭包允许代码块携带相关的变量环境,使其可以在不同的上下文中执行,而仍然能够访问原始数据。但 C 语言不具备这样的能力,所以我们需要另一种方式来实现类似的效果。

我们的方法是,在关键代码块中,使用一个变量来标识当前应执行的具体逻辑,而不是动态地传递函数或代码块。例如,在执行某些着色逻辑时,我们在代码内部插入了一个变量,根据该变量的值来决定执行哪种计算逻辑。这种方式避免了 C++ 中 lambda 表达式带来的复杂性,同时利用分支判断的开销相对较小,能够在性能和灵活性之间取得平衡。

虽然这种方法不能完全模拟闭包,例如它无法让不同代码块拥有各自独立的数据环境,也无法直接访问调用者的栈帧,但在实际使用中,它仍然是一种可行的解决方案。通过这种方式,我们能够在 C 语言中实现类似于闭包的功能,提升代码的可复用性和灵活性,同时避免引入过于复杂的语言特性。

在 C 语言中模拟闭包的示例

我们希望在不使用 C++ 的 lambdastd::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 结构体充当"闭包",它携带了 brightnesscontrast 两个参数,影响 processPixel 的计算方式。
  • drawRectangle 不需要知道具体的着色逻辑,它只是简单地调用 processPixel 并传入 ShaderContext,实现了逻辑解耦。

总结

  1. 第一种方法(全局变量 + switch

    • 适用于简单场景,只需要修改单一逻辑,而不需要额外参数。
    • 缺点是 currentMode 变量是全局的,不适用于多线程或需要多个独立实例的情况。
  2. 第二种方法(struct 传递环境变量)

    • 适用于更复杂的场景,比如不同的参数配置,或者在不同的地方同时应用不同的逻辑。
    • ShaderContext 允许 processPixel 访问额外的上下文信息,类似于闭包中的"绑定变量"。

在 C 语言中,这种方法虽然不像真正的闭包那样灵活,但可以有效实现类似的功能,同时避免 C++ 复杂的 lambdastd::function 机制带来的额外开销。

你为什么选择径向菜单而不是列表或其他格式?

我们选择径向菜单(Radial Menu)而不是列表(List)或其他布局方式的原因主要是其快速选择的特性

  1. 快速启用和关闭选项

    • 径向菜单适用于快速操作,用户可以通过鼠标或手柄的方向选择需要的选项,减少鼠标移动的距离,提高效率。
    • 适用于开关类选项,比如"启用/禁用调试模式"等。
  2. 直观的方向选择

    • 由于菜单项是围绕中心点分布,用户可以用方向性思维快速定位所需选项,而不像列表那样需要上下滚动寻找。
    • 适用于数量适中的选项(通常 6-8 个),不会占据过多屏幕空间。
  3. 减少视觉干扰

    • 列表或下拉菜单可能会占据较大面积,影响游戏界面的可视性,而径向菜单在短暂显示后可以快速消失,不干扰主要内容。

其他补充

  • 不仅限于径向菜单
    • 也会加入列表菜单,因为有些情况下列表比径向菜单更适合,特别是当选项较多、需要精确选择时,比如详细的设置或高级选项。
    • 可能会结合两种方式,径向菜单用于快速选择常见功能,而列表菜单提供更精细的控制。

总的来说,径向菜单适用于快速操作 ,而列表适用于更复杂或精细的选择,两者各有优劣,我们会在不同场景中使用合适的菜单类型。

如果菜单项与圆形边界对齐,那样文本碰撞的可能性会更小

我们计划对径向菜单进行一些优化,使其更直观和美观,同时减少文本重叠的问题。

优化方向

  1. 菜单项对齐圆形边界

    • 让菜单选项沿着圆的边缘排列,而不是随意分布,这样可以确保每个选项都均匀分布,减少视觉混乱。
    • 这样做还能让玩家更容易判断每个选项的位置,提升交互体验。
  2. 优化文本显示,避免重叠

    • 目前如果选项过多,文本可能会发生重叠,导致阅读困难。
    • 解决方案
      • 自动换行:当文本过长时,调整字号或进行换行,使其在有限空间内显示完整信息。
      • 缩略文本:如果菜单空间有限,可以在 hover 或选中时显示完整文本,避免影响整体布局。
      • 弧形文本对齐:让文本沿着径向菜单的曲线排列,使整体布局更加自然。
  3. 整体视觉调整

    • 增加一些 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。

相关推荐
虾球xz20 分钟前
游戏引擎学习第195天
c++·学习·游戏引擎
熙曦Sakura42 分钟前
【C++】map
前端·c++
Stardep1 小时前
算法学习11——滑动窗口——最大连续1的个数
数据结构·c++·学习·算法·leetcode·动态规划·牛客网
双叶8361 小时前
(C语言)学生信息表(学生管理系统)(基于通讯录改版)(正式版)(C语言项目)
c语言·开发语言·c++·算法·microsoft
wen__xvn1 小时前
每日一题洛谷P8716 [蓝桥杯 2020 省 AB2] 回文日期c++
c++·算法·蓝桥杯
rigidwill6662 小时前
LeetCode hot 100—二叉搜索树中第K小的元素
数据结构·c++·算法·leetcode·职场和发展
东京老树根2 小时前
SAP 学习笔记 - 系统移行业务 - MALSY(由Excel 移行到SAP 的收费工具)
笔记·学习
星途码客2 小时前
C++位运算精要:高效解题的利器
java·c++·算法
周Echo周3 小时前
5、vim编辑和shell编程【超详细】
java·linux·c++·后端·编辑器·vim
榆榆欸3 小时前
6.实现 Reactor 模式的 EventLoop 和 Server 类
linux·服务器·网络·c++·tcp/ip