手工制作的Hero Mac OS平台第005天,用Xcode调试(以及一些清理)。

Casey花了第五天的时间回答有关Windows图形的各种问题。 我想做些不同的事情。 我们已经在Mac上的图形方面有了一个很好的开端,但是我认为我们可以做一点清理,并照顾我的另一个宠物项目,使调试器在Xcode中运行。

在开始之前,对于文章和视频之间的代码稍有差异,我必须深表歉意。 录制Youtube视频时,有时我会稍后意识到,在给定的会话中,我可能会被咬得更多。 上一次,我没有包括文章中的一些清理工作,甚至更改了一些函数名称。

没什么大不了的,但是我只是想回到一种更标准的做事方式,我希望本文能够做到。 如果您喜欢按照文章进行操作,只需从Github上的第四天回购开始,那您就很好了。

目标

在本文的结尾,我想完成两件事。 拳头,我希望您对将代码从一个文件移动到另一个文件充满信心。 其次,即使您从未从Xcode生成项目,我也想向您展示如何在Xcode中使用调试器。

从现在开始到Mac OS平台层完成之前,该项目将发生很大变化,因此在开始处理编写自定义平台层的更雄心勃勃的方面之前,最好对代码进行处理。

移动自定义类型

如果您查看osx_main.mm的顶部,将会注意到很多定义和类型定义。 遵循原始的手工制作英雄,我们有意做到了这一点。

我(像Casey一样)想更清楚地了解“静态”一词的各种用法,并提请我们注意何时使用它来声明全局变量,内部变量或具有局部持久性特征的变量(有些事情我们还没有触及了)。

我们还定义了一些易于阅读的整数和无符号整数类型。 我们还将把它们包含在我们将要创建的handmade_types.h文件中。

您可以使用任何喜欢的文本编辑器来创建handmade_types.h文件。 我正在为此目的使用vim。

在代码目录中,键入以下内容:

  vim handmade_types.h 

在vim中,这将打开一个具有您指定名称的新文件。 您也可以使用vim打开osx_main.mm并使用以下命令打开带有新文件的新选项卡。

  :tabe handmade_types.h 

然后,要导航到下一个选项卡,请输入:tabn。 要导航到上一个选项卡,请键入:tabp。 您还可以执行一些奇特的操作,例如键入“ 3gt”,它将打开第三个选项卡(如果已打开)。

复制#define语句和各种整数typedef,然后将它们粘贴到handmade_types.h文件中,如下所示。

  #include  
  #define internal_variable静态 
#define local_persist静态
#define global_variable静态
  typedef int8_t int8; 
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
  typedef uint8_t uint8; 
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;

注意我们包含了stdint库这一事实。 这是因为它是我们创建基于c / c ++中现有整数类型的自定义整数类型所必需的。

现在回到osx_main.mm文件的顶部,并添加以下include语句。

  #include“ handmade_types.h” 

另外,请确保从osx_main.mm中删除刚复制的行。

此时,您应该能够运行build命令,并注意到该项目的构建与以前一样。 您还可以继续运行该应用程序,以确保您仍然处于功能平价状态。

移动HandmadeMainWindowDelegate

这不是您必须采用的约定,但是通常,当您有一个明确定义职责集的单独对象时,请将该对象放在单独的文件中。

我怀疑我们会将HandmadeMainWindowDelegate移到一个文件中,如果我们决定制作一个文件,该文件随后还将包含AppDelegate。

但是,在开始任何工作之前,我们需要为osx_main创建头文件。 这样,HandmadeMainWindowDelegate使用的所有全局变量和函数都可以使用。

如果您在vim中,请键入以下内容为osx_main创建头文件

  :tabe osx_main.h 

如果您使用的是Xcode,只需添加一个名为osx_main.h的新空白文件

盘点

HandmadeMainWindowDelegate使用了osx_main中的许多函数和变量。 这是我认为需要移植的内容。

 外部布尔运行 
macOSRefreshBuffer(NSWindow * window);
renderWeirdGradient();
macOSRedrawBuffer(NSWindow * window);

通过在osx_main.h文件中声明这些函数和变量,就可以将它们导入到我们稍后将创建的osx_handmade_delegates.mm文件中。

快速说明一下,如果要创建全局变量,请在其前使用“ extern”。 我们仅计划有一个全局变量来在需要时快速退出应用程序。

由于osx_main.h文件将使用一种自定义类型,因此请在顶部包含Handmade_types.h文件

  #include“ handmade_types.h” 

现在,粘贴前面代码部分中的四个项目,确保在函数之前添加void返回类型。

完成后,文件应如下所示

 外部布尔运行 
void macOSRefreshBuffer(NSWindow * window);
void renderWeirdGradient();
无效的macOSRedrawBuffer(NSWindow * window);

我们只是缺少一件事,那就是AppKit。 由于NSWindow来自AppKit,因此我们需要将其包含在任何引用该类型的文件中。

将此行添加到#include handmade_types.h所在行的正下方

  #include  

为了使其能够编译和维护功能奇偶校验,您需要将新的osx_main.h文件导入osx_main.mm文件。

将其添加到osx_main.mm的顶部,在其中包括handmade_types.h的行下方的一行

  #include“ osx_main.h” 

还要修改声明全局运行变量的行,如下所示:

  bool running = true; 

我们不再需要使用static了,因为static仅适用于我们要全局设置的变量,但对这个特定的.mm文件保持私有。 因此,在较早的时候,osx_main.mm的运行是“全局”的,而对于导入osx_main.h的任何文件,现在都是全局的。

为了确保一切都正常进行,请继续进行并构建项目。 您应该处于功能奇偶性且没有编译器错误。

移动Objective-C ++代码

使用您喜欢的文本编辑器创建一个名为osx_handmade_windows.h的新文件。 如果您跟随vim,则可以通过键入’tabe’命令和文件名来实现。

  :tabe osx_handmade_windows.h 

该文件将包含HandmadeMainWindowDelegate Objective-C类的公共接口,该类是我们的实用程序类,其唯一目的是拦截与窗口相关的事件(调整大小,关闭等)。

该文件将非常小。 只有三行。 将它们复制到文件中(并确保您摆脱了osx_main.mm文件中的声明)。

  #include  
  @interface HandmadeMainWindowDelegate:NSObject  
@结束

现在是时候创建包含实现的文件了。 在vim中键入以下内容以使用该文件名打开一个选项卡,或者仅在Xcode中使用该名称创建一个文件。

  :tabe osx_handmade_windows.mm 

该文件需要从其他文件中导入更多代码,但是范围相对较短。 将以下代码粘贴到文件中。

  #include“ osx_main.h” 
#import“ osx_handmade_windows.h”
  @implementation HandmadeMainWindowDelegate 
  -(void)windowWillClose:(id)sender { 
运行=假;
}
  -(void)windowDidResize:(NSNotification *)notification { 
NSWindow * window =(NSWindow *)notification.object;
macOSRefreshBuffer(window);
renderWeirdGradient();
macOSRedrawBuffer(window);
}
  @结束 

请记住,这与osx_main.mm中的实现代码相同,请从该文件中删除该代码,以免重复。

为了构建它,您需要将osx_handmade_main_window_delegate.h文件导入osx_main.mm文件。 这将使主要功能可以访问HandmadeMainWindowDelegate类(部分是。我们还必须修改构建脚本)。

在osx_main.mm的顶部包括以下内容

  #import“ osx_handmade_windows.h” 

修改构建脚本

每当添加新的实现文件时,都必须修改构建脚本以将其包括在“目标文件”部分中。 否则,clang编译器将无法找到已定义符号的实现(在本例中为HandmadeMainWindowDelegate文件)。

我玩了一个小时左右,然后决定最好学习一两个关于shell脚本的知识,这样我们就可以根据需要继续添加新的实现文件。

打开build.sh并回顾以下行(负责实际的编译)

 铛-g $ OSX_LD_FLAGS -o手工../handmade/code/osx_main.mm 

该行的最后部分引用了我们要包含在可执行目标中的文件之一。 但是,我们也想将osx_handmade_main_window_delegate.mm文件添加到该文件列表中。

如果要在引用osx_main.mm之后附加该文件名,请保存并运行生成脚本,您将获得一个可运行的可执行文件。

很好,一切都很好,但是这样做有很多原因。 首先,您将重复代码目录,该目录可能会提取到变量中。 其次,您还将使所有不同文件的编译行变得混乱。

我们应该提取其中一些信息,以保持编译行的干净和可读性。

首先,让我们提取手工的相对代码路径。 在OSX_LD_FLAGS之后添加以下变量

  HANDMADE_CODE_PATH =“ ../ handmade / code /” 

那只是相对于构建目录的代码路径。 相当容易。

接下来,您要创建一个变量,该变量代表希望clang编译并包含在目标中的所有文件。

在引用了HANDMADE_CODE_PATH的OSX_TARGET_INCLUDES之后创建一个新变量

大声笑或只是从屏幕快照中编写此代码,我将进一步解释😂

在Shell脚本中,当您将一个变量包含在字符串中的{方括号}内时,您将获得一个字符串,该变量的结果与随后的内容串联在一起。 知道这很方便。

注意,我们如何在编译行中对$ OSX_TARGET_INCLUDES进行单个引用。 超级干净!

现在,每次我们需要使用clang为某个Cocoa / AppKit对象编译另一个.mm文件时,我们只需将该文件添加到OSX_TARGET_INCLUDES部分即可。

在这一点上,您应该可以轻松运行shell脚本来构建游戏。 您还应该具有功能奇偶性。

目前,这已经结束了我们要做的一点清理工作。 现在,我们可以准确地知道将代码从开始的osx_main.mm文件中移出的位置。

关于何时正确移动代码,没有硬性规定。 随着时间的流逝,这是一种本能。

对我来说,当我很难对自己的代码做一个高级了解时,我可以告诉我需要移动代码。 有时候,最好能清楚地说明实现细节,但是当这些细节与某些子系统有更多共通之处时,子系统可能会在读取该文件中的代码时引起您的注意,而这会引起您的注意。

一旦存在关注点分离,现在可能是将逻辑转移到其他地方的好时机。 在我们的例子中,HandmadeMainWindowDelegate仅处理窗口事件,因此它可以存在于其他文件中。

在Xcode中使用调试器

您真的不认为我们会一直坚持到底,并始终在命令行中使用调试器(lldb),对吗? 尽管从原理上讲很酷,但从长远来看,最终可能会需要做更多的工作。 另外,如果您有这个非常不错的IDE,为什么不在这里和那里将它用于一些事情呢?

打开Xcode并创建一个新项目。 单击“跨平台”选项卡,然后在“其他”部分中选择“清空”。

将其命名为HandmadeHero(或任何您喜欢的名称),并将其保存到手工目录中。 我们可能会在以后移动它,但是现在就可以了,因为我们不会使用Xcode来构建游戏。

现在,在选择项目的情况下,转到文件->新建->目标

同样,您将有几种选择。 单击“跨平台”选项卡(它应该已经为您选择了),然后单击“聚合”。

我不太确定Aggregate到底是什么意思,但这是与代码相关的唯一真正跨平台的选择,所以我们要这样做。

同样,他们会问您一个名字。 只需将其命名为HandmadeHero。

此时,您的项目中应该有一个名为HandmadeHero的目标,以及用于构建它的构建方案。 Xcode应该看起来像这样。

我们将永远不会使用构建方案来构建项目,但是我们将使用该方案来告诉Xcode我们对调试感兴趣的可执行文件。

连接可执行文件

因为我们的clang build命令已经为我们生成了调试符号(您已经看到了handmade.dsym文件),所以我们只需要告诉Xcode调试时我们要查看哪个可执行文件即可。 为此,您需要编辑HandmadeHero方案。

单击播放按钮右侧顶部栏中的HandmadeHero目标图标,然后选择“编辑方案”。

在Xcode中,方案用于管理您可以对项目执行的各种操作。 例如,您可以构建,运行,测试,调试,配置文件,分析或存档您正在处理的项目。

出于附加调试器的目的,我们仅对设置的方案的“运行”部分感兴趣。

单击运行菜单选项。 接下来,单击“可执行”菜单选项,然后选择“其他”

这将提示您选择一个文件。 导航到构建目录并选择手工可执行文件。

如果正确完成此操作,则应该在方案编辑器窗口中将手作视为可执行文件。

大! 您的可执行文件已全部连接好,可以使用了。

添加文件进行调试并运行调试器

在这里要注意是非常重要的,因为完全有可能解决这个问题。 我确实做到了,而且花了我半个小时的时间在计算机上大喊“ wtf”。

如果要在Xcode中使用调试器,则必须将要调试的文件添加到项目中。

一切都很好,但是Xcode有一些您绝对想避免的时髦行为。 其中之一是“如果需要复制项目”选项。

这个选项会给您带来极大的麻烦,因为它将复制文件,然后将其放在Xcode项目目录(而不是代码目录)下,这意味着Xcode永远不会遇到断点,因为Xcode所查看的复制文件不是编译器生成的内容使游戏成为可执行文件。

打开代码目录并将osx_main.mm文件拖到项目中。 您应该看到以下对话框。

我重复一遍,不要选择任何东西。 只需单击完成。 这将告诉Xcode将文件视为参考,而不要像将文件复制到与我们的自定义构建系统无关的单独项目目录中那样愚蠢地做任何事情。

在osx_main.mm的main函数中放置一个断点,然后选择Product-> Run

Xcode会告诉您产品构建成功(我知道这很有趣),游戏将启动,调试器将达到断点。

凉! 我们不再需要使用命令行来调试应用程序。 我们可以简单地引用我们要在Xcode中查看的文件,放入一些断点,然后以更自然的方式进行调试。

每当有要调试的文件时,只需单击并将其拖到Xcode项目中,切记不要单击任何奇特的选项!

支持此内容

如果您发现此内容有价值,并且希望看到更多内容,可以在Patreon上进行支持。

任何数量的帮助。

也许您正在尝试打入游戏行业,或者您只是想成为一个更好的程序员。 在本地大学注册高级编程课程需要花费多少钱? 您可以为发布跨平台游戏的Unity许可证支付什么费用?

您几乎可以从那里看到的每个教程都是从一个谜团开始的。 您不知道构建系统如何工作。 您只需要使用它。 您不知道这个或那个框架如何解决您的问题。 您只需要使用它们。

我们在这里做的事情完全不同。 我们没有质疑现有系统的价值,而是质疑我们所看到的并从头开始构建更好的东西。 如果我们不了解它,我们会戳它并刺刺它,直到我们明白为止。

我会在业余时间免费进行100%的操作。 每一项贡献都有助于我微不足道地全职从事这项工作。 如果我能够通过这项工作来支持自己,那将释放出大量的内容,我认为这些内容将使我们所有人都变得更好。

所以这又是那个链接。 在Patreon上支持此内容。 感谢您的阅读,如果您学到了一些有价值的东西,请在下面评论。

下一步是什么?

知道该项目处于更加干净的状态,我感觉好多了。 我们不仅可以将代码从osx_main.mm文件中移出,而且还可以在Xcode中进行调试。 这将使将来更轻松地使用此项目。

如果您想和我一起检查您的作品,请查看本系列第5天的Github回购。

在下一部分中,我们将研究与平台无关的游戏手柄和键盘输入(就像Casey一样)。 我们将连接一个游戏控制器,并使其修改在屏幕上绘制的内容。

如果您认为这项工作很有价值,请鼓掌并与您的朋友分享。 另外,请务必支持Handmade Hero。 没有它,这是不可能的。

谢谢,下次见!