仓库:https://gitee.com/mrxiao_com/2d_game
回顾
回顾了他们的游戏开发进度,并强调了编写整个游戏的价值。他们提到,这个过程的目的是让每个参与者从零开始编程一个完整的游戏,了解整个游戏的工作原理。这样做的一个关键好处是,开发者不再局限于使用别人写的工具或引擎,而是可以自己编写引擎或库的一部分,甚至整个游戏引擎,从而更好地理解和掌控自己的开发过程。
此外,讲述者解释了,这种从头编写游戏的经验对开发者来说非常重要,因为它使得开发者能够理解他们所做的事情对性能的影响,即使他们最终使用的是别人写的引擎。这种知识使得开发者成为更优秀的程序员,因为他们能更好地判断并优化自己的代码和选择。
接下来,回顾上周的工作进展,目前的游戏状态。游戏中的主角是一个小矩形(黄色的),它在灰色和白色的矩形上移动,作为玩家的测试角色。开发者通过这种简单的占位符,验证了他们可以实现一些基础功能,比如角色在地图上移动、通过门等。这个阶段的目的是为引擎设计打下基础,确保它能够支持他们想要做的事情。
在讨论过程中,讲述者还指出,虽然他们正在做这些基础的工作,但也有许多需要探讨的问题。他们强调,开发过程中需要谨慎,以免做出错误的决策,因此需要先确定游戏引擎的基本需求,明确如何实现它们,确保引擎的设计既简洁又高效。
最后,讲述者澄清了一些观众的疑虑,特别是关于游戏设计的部分。他强调,虽然游戏开发过程会涉及设计工作,但他并不是一个专业的游戏设计师,因此他不会在直播中教授游戏设计。他指出,游戏设计是他们开发过程中一个重要但独立的环节,他们会在开发时处理设计问题,但并不会专门教授游戏设计课程。
关于滚动
解释了游戏设计的一个关键决定,特别是关于游戏是否包含滚动背景的问题。虽然他选择让游戏采用固定屏幕(类似于《塞尔达传说》的早期版本),这并不是一个武断的决定,而是经过深思熟虑的设计选择。他能够为这一选择辩护,说明它对游戏的整体设计有着独特的优势。
尽管如此,一些人可能更倾向于游戏中有一个滚动背景,并且对这种设计方式有疑虑。如果有玩家对滚动背景的实现感兴趣,并且有类似的游戏设计想法,这也是完全有效的。希望通过这次讨论,能展示给观众如何将滚动背景与固定背景的设计之间的差异进行比较,强调其实它们在实现上并没有根本的不同。
尽管当前选择了固定背景,但计划在接下来的几天里展示如何实现滚动背景的功能。通过这一过程,希望向观众展示,如何使用现有的瓦片地图实现滚动背景,并且说明这些设计选择如何能够在不改变游戏本质的情况下灵活实现。
自由滚动
在设计游戏时,有一项重要要求是确保没有加载屏幕或停顿,以保证玩家体验流畅。目标是在游戏中,尤其是在角色移动时,能够无缝地从一个屏幕跳转到另一个屏幕。具体来说,当一个黄色矩形(可能是游戏角色)从屏幕的一边移动到另一边时,游戏不允许有任何暂停或卡顿现象。无论是从当前屏幕到下一个屏幕,还是从底部屏幕到下方的屏幕,都必须立即显示,确保过渡的流畅性。
对于这种流畅的过渡,核心技术点是引擎能够及时绘制出屏幕的内容,并且可以快速切换显示不同的屏幕。引擎的设计必须能够支持在任何时刻展示出周围的八个屏幕,确保画面无缝衔接。
虽然考虑到可能需要滚动游戏画面,但实际上,滚动并不会带来太大问题,只需要通过移动相机视角来调整画面内容,从而实现平滑的过渡。这种设计能够保证画面切换和移动过程中的平滑体验,不会造成停顿或延迟。
然而,滚动并不总是被采纳,因为有一些游戏设计的要求排除了滚动的使用,这种情况下滚动功能就不能有效工作。尽管如此,如果确实需要滚动技术,可以通过简单的调整使之生效,但此时并不打算将滚动作为默认选项使用。
最后,虽然可以开发支持滚动的引擎,但并不打算在这款游戏中实现滚动。这样做是为了避免某些特定设计要求的冲突,从而确保游戏的其他功能正常运行。因此,决定暂时不启用滚动功能。
即将到来的内容预览:数学函数
在项目中,数学函数扮演着重要角色,尤其是在处理浮点数数学操作时。具体来说,曾经使用过 sign
函数和 floor
函数来生成正弦波,后者将值移到下一个较低的整数。这些数学函数在 C 运行时库中有实际实现,常用于各种数学计算,包括三角函数和离散数学函数。比如,ceil
和 floor
函数帮助处理数值的向上或向下取整,这些函数在离散数学中更为常见,而不是高中数学中常讨论的连续函数。
在讨论这些数学函数时,也提到了 C 运行时库中的其他函数,它们能够支持基本的浮点数运算。这些函数看似和高中数学课程中的内容类似,包括三角函数(如正弦、余弦、正切及其反函数)和对数、指数运算等。虽然一些函数可能比较陌生,但它们依然是基本的数学运算工具。
在当前的开发过程中,处理这些数学问题是必要的,尤其是当涉及到像正弦波这种特定的计算时。项目的目标之一是完成平铺地图的实现,这要求对这些数学运算有所依赖和熟悉。
此外,提到在调试过程中遇到的问题,尤其是与调试符号(PDB 文件)加载相关的错误。尽管函数本应被调用,但某些函数却没有执行,这被认为是一个异常现象。这个问题可能与调试设置有关,尤其是在使用 Visual Studio 时,这是一种常见的调试难题,可能需要进一步的解决方案。
看看 floorf
在讨论 floor
函数时,首先提到通过右键点击并拆卸查看该函数的实际工作原理。 floor
函数属于 C 运行时库,其作用是将给定的浮点数向下舍入至最接近的整数。但实际操作中,这个函数会执行一系列复杂的操作,做了很多额外的工作,这使得它的性能相对较低。在执行过程中,floor
函数进行了大量的步骤,最终才得到想要的结果。
然而,这些额外的操作对于某些应用来说可能是多余的,因为对于项目的需求,只需要 floor
函数完成一项简单的任务——将一个数值“拉”到数轴上的下一个整数。这个过程的复杂性与项目需求之间存在较大的不匹配,因此有理由考虑是否可以采用更高效、简单的方式来实现这一功能。
问题的核心是,当前使用的 C 运行时库中的 floor
函数虽然遵循 C 标准并完成其工作,但由于其内部的复杂实现,可能不适合特定的项目需求。也许可以通过其他方法,避免使用这么复杂的库函数,从而提高效率,并且不必严格遵守 C 标准。这样的话,可能会找到一个更加简化且符合实际需求的解决方案。
SSE2
AVX(高级向量扩展)、SSE(流式SIMD扩展)和MMX(多媒体扩展)是为x86架构设计的指令集扩展,旨在提高处理性能,尤其是在涉及向量化处理的任务中,如多媒体处理、科学计算和加密等。它们允许CPU同时处理多个数据点,从而提高特定任务的吞吐量。以下是它们的详细解释:
1. MMX(多媒体扩展)
- 发布年份:1997年,英特尔在Pentium MMX处理器中推出。
- 目的:主要面向多媒体应用,如视频和音频编码/解码、图像处理和3D图形。
- 功能:MMX引入了64位的SIMD(单指令多数据)寄存器,可以同时处理多个8位或16位的整数。
- 局限性:MMX在数据类型上支持有限,并且它与浮点寄存器不兼容,使用MMX可能会影响浮点操作。因此,MMX最终被SSE取代,SSE在向量化处理上提供了更多灵活性和性能。
2. SSE(流式SIMD扩展)
- 发布年份:1999年,英特尔在Pentium III处理器中推出。
- 目的:扩展MMX,提升浮点数、向量化操作和3D渲染任务的性能。
- 功能:SSE引入了128位的寄存器(XMM寄存器),支持浮点数的SIMD操作,可以在一个指令中同时处理四个单精度浮点数。SSE还改善了多媒体应用的支持,比MMX更灵活。
- 版本:
- SSE:原始版本,支持基本的128位SIMD操作。
- SSE2(2001年):扩展SSE,支持双精度浮点操作,并增加了更多的指令。
- SSE3(2004年):引入了一些新指令,用于特定的向量和标量操作,同时改进了多线程支持。
- SSSE3(补充SSE3,2006年):增加了用于加速某些算法的指令。
- SSE4.x(2006–2008年):新增50多个指令,包括字符串处理、点积和其他高级操作。
3. AVX(高级向量扩展)
- 发布年份:2011年,英特尔在Sandy Bridge架构中推出。
- 目的:提供更强大的SIMD能力,特别是在浮点运算和向量应用中。旨在提升科学计算、加密和多媒体工作负载的性能。
- 功能:AVX扩展了SIMD的能力,使用256位的寄存器(YMM寄存器),相比SSE的128位寄存器,能够并行处理更多的数据。这也显著提升了浮点操作的性能,支持单精度和双精度浮点数的运算。
- AVX:引入了256位的YMM寄存器,并扩展了向量化浮点操作的指令集。
- AVX2(2013年):支持完整的256位整数SIMD操作,并引入了许多新的指令(例如,支持随机内存访问的gather指令)。
- AVX-512(2016年):进一步扩展了SIMD能力,使用512位的ZMM寄存器,为高性能计算任务提供更多并行性。
- AVX-512目前主要用于Intel Skylake-X及后续处理器,虽然性能上优于SSE和AVX,但由于其更高的功耗要求,某些典型工作负载可能无法充分利用AVX-512的优势。
关键区别:
-
寄存器大小:
- MMX使用64位寄存器。
- SSE使用128位的XMM寄存器。
- AVX使用256位的YMM寄存器(AVX2继续使用256位)。
- AVX-512进一步扩展到512位的ZMM寄存器。
-
性能:
- AVX和AVX2相比MMX和SSE有显著的提升,因为它们拥有更大的寄存器和更复杂的指令集,能够提供更高的并行性和计算效率。
-
精度:
- MMX仅支持整数运算。
- SSE支持整数和浮点数运算。
- AVX支持单精度(32位)和双精度(64位)浮点运算,提供更高精度的计算。
-
兼容性:
- 较旧的处理器仅支持MMX和SSE,而现代处理器(如英特尔Sandy Bridge及后续处理器)则支持AVX、AVX2和AVX-512。AVX相对于SSE在某些工作负载中提供了显著的性能提升。
开发中的应用:
在实际开发中,开发者可以通过汇编语言直接利用这些指令集,或者通过高层次的库(如Intel的Math Kernel Library(MKL)或苹果的Accelerate框架)来优化SIMD指令的使用。这些库对矩阵乘法、傅里叶变换等重计算任务进行了高度优化,能够利用AVX和SSE等指令集来加速计算。
总结:
- MMX:较老的整数SIMD扩展,现已被淘汰。
- SSE:引入了128位SIMD,支持浮点数运算,适用于多媒体和科学计算等任务。
- AVX:引入了256位SIMD,显著提升了浮点运算性能,适合需要大量并行处理的应用。AVX-512进一步扩展至512位,适用于高性能计算任务。
对于现代应用来说,AVX2通常是最有益的指令集,而AVX-512则适用于需要最大并行度的高性能计算,如科学计算或机器学习任务。
AMD x64(通常称为 AMD64)是由 AMD(Advanced Micro Devices)推出的 64 位扩展架构,它在 2003 年首次发布,并且是 x86 指令集的 64 位扩展。与当时的 32 位架构(x86)相比,AMD64 提供了显著的性能提升,尤其在处理大规模内存、高性能计算和更复杂的多线程任务时。
1. AMD x64(AMD64架构)概述
- 发布年份:2003年,AMD发布了基于其64位架构的 Opteron 和 Athlon 64 处理器。
- 兼容性:AMD64 架构向后兼容 x86(32位架构),即可以运行32位和64位的操作系统和应用程序。这意味着用户可以在同一台计算机上运行32位的软件,同时享受64位硬件带来的性能优势。
- 64位扩展:AMD64 对 x86 指令集进行扩展,支持 64 位寄存器和指令。这使得它能够更高效地处理大量数据,特别是在内存访问和处理器计算能力上。
2. AMD64 和 Intel 64(EM64T)的关系
- AMD64 是 AMD 推出的 64 位扩展架构,而 Intel 64(早期称为 EM64T)是 Intel 推出的类似扩展架构。虽然它们有一些不同的实现细节,但在设计上基本相同,两者都使用类似的指令集,因此软件和操作系统可以在这两种架构上无缝运行。
- 事实上,Intel 64 和 AMD64 在很多方面是兼容的,Intel 在其处理器中实现了类似于 AMD64 的指令集,并且它们的操作系统支持和程序也能互操作。
3. AMD64的关键特性
- 64位寄存器和地址空间:AMD64 处理器可以使用 64 位寄存器,相较于 32 位架构,可以处理更大的数据集,支持最多 16 EB(Exabytes) 的虚拟地址空间(远远超出了目前硬件的实际支持)。
- 更大的内存寻址:传统的 32 位系统最多只能寻址 4 GB 的内存,而 AMD64 支持的64位寻址理论上允许系统访问更大的内存空间(超过 4 GB,通常支持到 TB 级别)。
- 增强的指令集:AMD64 增强了原有的 x86 指令集,并增加了一些新的指令,以支持 64 位操作。这些指令可以加速大型数据集的处理和计算密集型任务,如加密和科学计算。
- 硬件虚拟化支持:AMD64 处理器通常支持硬件虚拟化(AMD-V),这意味着可以在硬件级别上加速虚拟机的执行,并提高虚拟化应用的性能。
4. 与32位系统的兼容性
- 向下兼容:AMD64 是向下兼容的,即可以在 64 位处理器上运行 32 位的操作系统和应用程序。系统启动时可以选择加载 32 位操作系统,或者运行 32 位应用程序,而不必担心兼容性问题。
- 运行 64 位应用程序:如果操作系统和应用程序支持 64 位,那么可以利用更多的内存(超过 4 GB)和更强的计算能力。大部分现代操作系统(如 Windows 和 Linux)都支持 64 位版本。
- 性能提升:对于内存密集型的应用(如数据库、大型科学计算、视频处理等),64 位架构提供了显著的性能提升,因为它能够更高效地处理大数据集,并提高内存带宽。
5. AMD64 的使用场景
- 高性能计算:由于更大的内存寻址和改进的并行计算能力,AMD64 架构广泛应用于高性能计算(HPC)领域,比如科学研究、金融建模、数据分析等。
- 服务器和工作站:AMD64 的高内存支持和高效的多任务处理使其在服务器和工作站中尤为重要。这些系统需要能够处理大量并发任务和大规模数据。
- 个人计算机和游戏:现代的台式机和笔记本计算机普遍采用 AMD64 架构。对于游戏、图形设计、视频编辑等任务,AMD64 提供了更强的计算能力和多任务处理能力。
- 虚拟化:AMD64 还在虚拟化技术中发挥重要作用,支持虚拟机运行在 64 位操作系统上,可以利用更多的系统资源(如更多的内存和更强的 CPU 性能)。
6. AMD64的演变
随着技术的进步,AMD64架构经历了多个版本的改进,提供了更强的性能和更高效的指令集。比如:
- SSE/SSE2/AVX扩展:AMD64 处理器支持类似于 Intel 的 SIMD 扩展(如 SSE, AVX 等),用于加速并行计算和多媒体任务。
- Zen架构:AMD的 Zen 架构(包括 Zen 2, Zen 3, 和后续版本)大大提升了AMD64处理器的性能,尤其在多核处理和能效方面,已经与 Intel 竞争并超越。
7. 总结
AMD64 是一种为 64 位处理器设计的架构,提供了更大的地址空间、更强的计算能力以及更高效的内存管理。它广泛应用于现代的个人电脑、工作站和服务器,并且在高性能计算、虚拟化和大数据处理等领域发挥着重要作用。它的向后兼容性和对多种操作系统的支持使得开发者和用户可以轻松地过渡到更高效的 64 位环境。
优化标志:让编译器为你做这件事
-
编译器优化:编译器在进行代码编译时可以执行一系列优化,通常通过开启不同的优化级别来改善代码的执行效率。例如,可以使用编译器的优化选项来启用或禁用某些特性,使得代码在执行时能尽可能高效。
-
关闭优化与内联函数:在一些情况下,为了调试或检查具体的实现,可能会关闭编译器优化。关闭优化后,代码可能会以更直接的方式执行,便于检查每个步骤的细节。比如,关闭优化可能导致某些函数被内联处理,这样函数的具体实现会直接嵌入到调用处,而不是作为独立的函数调用。
-
使用编译器选项进行定制:有些情况下,可以通过设置编译器选项(如
-O
优化标志)来启用特定的优化。例如,如果希望对浮点数运算进行特别处理,可以启用相应的优化选项。也可以通过编译器的某些开关来控制程序行为,避免某些操作影响结果的准确性(如浮点数的一致性)。 -
尝试编译器智能化:有时,编译器能够智能地进行优化并执行某些更高效的操作。虽然可以通过编译器设置来引导编译器做出更聪明的决定,但有时这种智能化操作并不总是符合预期,或者可能不完全符合某些规范。因此,并不总是依赖于编译器来做这些优化。
-
自定义实现与控制:对于某些功能(如数学操作),即使编译器能够进行一定优化,仍然可能需要实现自定义版本来确保程序按照需求执行。这些自定义实现能够更加精确地控制程序的行为,并确保其只执行所需操作,避免不必要的开销。
-
调试和优化:在调试过程中,可能需要查看编译器如何处理特定的函数,比如是否进行了内联或优化。通过设置断点,可以查看编译器是否已经应用了优化,或者是否有其他潜在的性能问题。
-
编译器的灵活性与不确定性:虽然可以通过编译器选项控制优化行为,但有时并不能完全信任编译器的智能优化,因此可能会依赖手动控制和自定义实现。这意味着对于关键的操作,最好还是手动干预,而不是完全依赖编译器来处理。
编译器内建函数(Compiler Intrinsics) 是编译器提供的特殊函数或操作,它们允许直接访问硬件特性或优化,这些特性通常在标准编程语言中不可用。编译器内建函数通常对应于处理器指令集中的特定指令或功能,编译器可以利用它们优化某些操作。
编译器内建函数的关键点
-
访问低级硬件特性:内建函数用于直接访问处理器特定的功能,例如 SIMD(单指令多数据)指令、向量运算或硬件优化。它们提供了一种接口,可以在不编写汇编代码的情况下利用这些硬件特性。
-
平台特定:由于内建函数通常与特定处理器或架构相关,因此它们是平台特定的。例如,Intel 提供了一套用于其处理器的内建函数(如 SSE、AVX 和 AVX-512),而 AMD 提供了一套适用于其处理器的内建函数。
-
性能优化:编译器内建函数可以通过利用硬件功能来显著提高性能,通常在对数组或大量数据进行并行处理时特别有效。它们帮助编译器生成更高效的机器代码,使代码在特定的处理器上运行得更快。
-
高层语言编写:虽然内建函数通常在高层语言(如 C、C++ 或 Fortran)中使用,但它们会映射到与硬件相关的机器指令。这样,程序员可以在不编写汇编代码的情况下,直接访问硬件优化。
编译器内建函数的类型
-
SIMD 内建函数:这些内建函数允许程序员利用 SIMD 指令进行向量化操作,常用于数组或大数据集的操作,使处理器能够并行处理多个数据元素。
- SSE(流式 SIMD 扩展):Intel 的 SIMD 指令集,用于加速多媒体任务。内建函数如
_mm_add_ps
、_mm_mul_ps
用于并行加法和乘法操作。 - AVX(高级向量扩展):AVX 是对 SSE 的扩展,提供更宽的 256 位和 512 位向量操作,允许更高的并行度。内建函数如
_mm256_add_ps
用于这些操作。
- SSE(流式 SIMD 扩展):Intel 的 SIMD 指令集,用于加速多媒体任务。内建函数如
-
数学内建函数:一些内建函数与特定的数学操作相关,例如平方根、三角函数等,它们可能经过优化以利用特定处理器的指令集。
- 例如,
sin
和cos
函数可能作为内建函数实现,以便利用处理器的专门指令,使这些操作更快速。
- 例如,
-
原子操作:用于多线程编程中的内建函数,例如原子比较并交换,用于保证共享变量的操作是线程安全的。它们通常与处理器的锁操作和内存屏障特性相关联。
-
位操作内建函数:用于直接操作数据的位。例如,位旋转或人口计数(计算数据中设定位的数量)可以作为内建函数提供。
-
内存内建函数:这些内建函数用于直接操作内存,如预取数据、或者内存屏障,以确保内存访问顺序。
- 例如,
_mm_prefetch()
用于数据预取,_mm_stream_ps()
用于 SSE/AVX 中的流式存储。
- 例如,
-
控制内建函数:用于控制程序的流程,例如分支预测或控制流优化。这些内建函数可能优化条件分支或其他处理器执行指令的方式。
使用内建函数的代码示例(C/C++)
#include <immintrin.h>
void add_vectors(float* A, float* B, float* C, int size) {
for (int i = 0; i < size; i += 8) {
__m256 a = _mm256_load_ps(&A[i]); // 从 A 加载 8 个元素
__m256 b = _mm256_load_ps(&B[i]); // 从 B 加载 8 个元素
__m256 result = _mm256_add_ps(a, b); // 并行加法
_mm256_store_ps(&C[i], result); // 将结果存储到 C
}
}
在上面的示例中,__m256
类型用于保存 8 个浮点数(即 256 位),而 _mm256_add_ps
内建函数用于并行加法操作。
使用内建函数的好处
-
性能:通过利用特定的处理器指令,内建函数可以显著提高性能,尤其是在数据密集型任务中,如科学计算、多媒体处理或加密算法。
-
可移植性:与直接编写汇编代码相比,内建函数通常能够支持多个编译器和处理器平台,因此它们比汇编代码更具可移植性。
-
简洁性:内建函数比汇编语言更容易使用,允许开发者在无需编写低级汇编代码的情况下进行优化。
-
控制:通过内建函数,开发者可以更精确地控制程序与硬件的交互,这在性能要求极高的应用中至关重要。
使用内建函数的缺点
-
可移植性问题:由于内建函数通常与特定的硬件平台相关,因此使用了内建函数的代码可能无法在不支持这些指令的处理器上运行。例如,使用 AVX 指令集的程序在不支持 AVX 的老旧处理器上可能无法运行。
-
复杂性:内建函数可能会增加代码的复杂性,尤其是在使用大量低级优化时。代码可读性可能下降,且可能使得其他开发者难以理解代码的意图。
-
依赖编译器:不同的编译器可能支持不同的内建函数,因此在不同编译器之间,代码的优化效果可能不一致,某些编译器可能无法生成与另一个编译器相同的优化代码。
常见的内建函数库
- Intel 内建函数:Intel 提供了一套针对其处理器的内建函数,特别是 SSE、AVX 和 AVX-512。这些内建函数在 Intel 内建函数指南中有详细文档。
- AMD 内建函数:AMD 提供的内建函数与 Intel 的类似,通常是针对 SSE 和 AVX 指令集的扩展,但它们也可能包含一些专有功能。
- MSVC 内建函数:微软 Visual Studio 编译器提供了用于 SIMD、原子操作和其他优化的内建函数。
- GCC 和 Clang:这些开源编译器也提供用于 SIMD 和其他性能优化的内建函数。
结论
编译器内建函数 是一种强大的工具,开发人员可以通过它直接访问硬件特性,从而优化程序。通过利用这些内建函数,程序可以显著提高性能,尤其是在数据密集型任务中。然而,内建函数通常与硬件平台紧密相关,可能导致可移植性问题,因此应谨慎使用,确保代码在不同平台上的兼容性和可维护性。
数学工具层
1. 代码迁移和优化
- 目标是将一些数学相关的函数迁移到一个专门的数学文件中,使其能够根据平台的不同进行优化。
- 需要确保函数在不同平台上能够高效地执行,具体来说是根据平台的硬件特性来优化函数实现。
- 计划将一些现有的数学函数(比如
floor
)从现有代码中移除,并替换为平台特定的实现,以提高性能。 - 这种迁移不仅限于
floor
函数,还包括其他数学函数(如正弦函数、海岸线函数等),并且需要仔细考虑如何根据平台来实现这些函数。
2. 平台特定的实现
- 通过为每个平台设计特定版本的函数,可以更好地利用平台的硬件能力(如 SIMD 指令集、AVX、SSE 等)。
- 这一过程将需要在代码中大量修改,并且花费时间来确保不同的硬件架构能高效执行这些函数。
3. 处理器和数学函数
- 处理器的能力(如是否支持特定的数学运算)会影响到数学函数的实现。例如,某些处理器可能没有内建的符号函数,这时候就需要通过其他方法实现这些功能。
- 需要在代码中仔细处理如何实现这些数学操作,特别是像三角函数、符号函数等复杂的数学计算。
4. 避免不必要的函数调用
- 目标是避免在代码中频繁调用某些低效的函数(如
math.h
),而是集中管理和优化这些函数调用。这样可以更清晰地控制代码的性能。 - 通过集中管理函数的实现和调用,能够更容易地做出平台特定的优化,并避免重复和不必要的操作。
5. 讨论三角函数和向量
- 提到了三角函数(如正弦函数)和向量操作,这可能是为了在未来的工作中为数学计算提供更高效的实现。
- 在进行数学优化时,向量计算也将是一个重要的部分,因为可以利用 SIMD 指令并行处理多个数据,从而提高性能。
6. 开发过程中的挑战
- 开发人员需要处理的挑战包括:如何在低层次上实现这些数学函数,如何根据平台的不同特性来进行优化,以及如何高效地实现这些函数而不影响程序的可维护性。
- 由于处理器和硬件的差异,不同平台可能需要不同的实现方法,这意味着在开发过程中需要投入大量的精力去处理平台特定的优化。
7. 时间投入与实现细节
- 实现这些优化和迁移将需要一定的时间,因为不仅要设计合适的数学函数,还要确保它们在不同平台上能够正常工作并充分利用硬件特性。
- 还需要关注如何处理符号、三角函数等复杂的数学计算,因为这些操作在不同平台上的实现方式可能会有所不同。
总结:
- 目标是通过迁移和优化代码,使其能够根据平台的不同进行高效的执行,特别是针对数学计算方面的优化。
- 在实现过程中,需要考虑到处理器的硬件特性和性能要求,采用适合的算法和实现方式,并确保代码的可维护性和效率。
坐标系统:为什么我们需要它们
在实现瓦片地图时,当前的假设是基于像素进行处理。每一项操作,如瓦片大小和物体的移动,都是按照像素来计算的。然而,这样的假设会带来一个问题,如果分辨率发生变化,代码就会变得不适用。举个例子,假设改变了瓦片的大小从原本的60像素变为30像素,那么物体的移动速度将加快,偏移量也会发生错误。这是因为所有的操作都基于像素单位进行,而像素在图形显示上并没有实际意义。
这种变化的问题会在游戏运行时暴露出来,特别是在需要支持不同分辨率时。尽管可能很长一段时间都不会遇到这个问题,但为了避免潜在的错误,必须开始考虑这个问题。这是一个非常现实的挑战,因此需要调整设计,避免假设单位为像素。实际上,游戏中应该有自己的单位,这些单位不一定是像素,且不应基于像素。
此外,另一个需要考虑的重要概念是坐标系统。游戏中的单位和坐标系统需要重新思考,确保即使改变分辨率或瓦片大小,游戏的表现依然正常。
屏幕坐标与所有数学
在开发过程中,有两个主要问题需要解决:
-
像素单位与世界单位的差异:当前的系统假设一切都基于像素作为单位,这意味着如果瓦片的分辨率发生变化,物体的移动和其他行为将受到影响。如果改变瓦片的大小,移动的速度会加快,偏移量也会出错。这是因为所有操作都基于屏幕像素,像素并不一定代表游戏中的真实单位,因此需要将单位从像素转换为更有意义的世界单位,以避免因分辨率改变而导致的问题。
-
坐标系的反转:当前的坐标系统与数学中标准的坐标系不一致。在数学中,通常y轴是正方向向上的,而在当前的游戏坐标系统中,y轴是负方向向下。这个反转的问题可能在后续开发中引发混乱,特别是在涉及角度和物理计算时。虽然没有绝对的理由强制使用数学标准,但如果不调整,将在调试和开发过程中遇到不必要的复杂性。通过与数学教材一致的坐标系统,可以减少这种困惑,避免后期的错误。
总结来说,解决这两个问题能够使游戏引擎更健壮、更容易维护。如果不考虑这些问题,游戏也可以运行,但在多分辨率支持和物理计算等方面,可能会遇到不必要的麻烦。提前处理这些问题,将有助于避免开发过程中的“头痛”,并提高代码的可移植性和可扩展性。
定义像素和米的大小
首先提到了使用现实世界单位来帮助理解虚拟世界的尺度。这样做的目的是使事物的大小变得更加可理解,并通过实际的测量单位(如米)来设计和调整游戏世界中的物体,比如重力等。这种方法提供了一种更直观的方式来定义和调整游戏中的物理属性,而不是完全凭空猜测。
讨论进一步深入到对一个虚拟角色的规模估计。例如,考虑到一个10岁男孩的平均身高约为1.4米,这个数字被用作游戏中角色的尺度参考。这意味着虚拟世界中的一个“瓦片”的尺寸可能约为1.4米,作为游戏设计的基准。通过这种方法,开发者可以将现实世界的尺寸映射到虚拟世界,进而帮助确定物体的相对大小。
接着,如何将这些尺度转换为游戏中的像素尺寸。为了避免图形模糊,特别是在渲染时,决定采用整数像素,而不是让像素数值成为分数。虽然这样做可能在某些情况下不够灵活,但它保证了图形渲染时的清晰度。
在整个过程中,强调了如何将现实世界单位(如米和像素)结合使用,确保游戏世界中的物体和角色能与现实世界进行合适的比例映射。这种方法不仅有助于定义虚拟世界的物理特性,还能确保游戏中的元素能够在屏幕上呈现出恰当的大小和位置。
最后,还提到了一些开发过程中需要进一步完善的部分,如对地图压缩的改进,计划在接下来的开发阶段继续进行调整和优化。这些步骤都是为了确保游戏中的尺度和物理效果能够达到预期,提供更加沉浸的游戏体验。
打包我们的瓷砖地图索引
在这段讨论中,重点是如何处理游戏中的地图和瓦片(tile)。最初提到的关键点是,游戏开发过程中,需要从像素单位转换为现实世界单位,以便更加精确地控制瓦片的位置和地图的尺寸。讨论的一个重要目标是优化瓦片地图的表示方式,通过虚拟内存技术来管理大型地图的存储和渲染。
-
瓦片的尺寸与单位:
- 讨论者提到,当前使用的是像素作为单位来表示瓦片的尺寸(例如每个瓦片的尺寸为60像素)。但随着开发的进展,计划逐渐过渡到现实世界单位(例如米),以便更好地表示和处理地图的位置和尺寸。
-
瓦片地图的构建:
- 瓦片地图是由多个瓦片组成的网格。每个瓦片可以看作是地图中的一个单元,通过使用整数坐标来表示其位置。瓦片的排列方式基于某种坐标系统,比如网格坐标系。之前提到的原始位置是通过瓦片的x和y坐标来表示的。
-
内存管理与虚拟内存系统:
- 随着地图规模的增加,直接存储整个世界地图会显得不现实,因为即使是四十亿乘四十亿大小的地图也远远超出当前计算机的内存限制。因此,提出了将地图划分成多个较小的“块”,并采用虚拟内存方式来管理这些块的存储。每个“块”只包含地图的一部分,这样可以根据需要动态加载和渲染地图的各个部分。
-
数字打包与位操作:
- 为了更高效地表示瓦片的坐标和索引,提到了将x和y坐标以及瓦片的索引打包成单一的32位整数值。这些整数值通过分割为高位和低位来分别表示不同的信息。例如,低位可以用于存储瓦片的具体位置,而高位可以用于表示所在的“页”或地图的部分。这样做的目的是简化内存使用,同时保持灵活性。
-
处理地图大小:
- 讨论中提到,支持的最大地图尺寸是40亿乘40亿米,足够覆盖整个地球的表面。这种尺寸对于绝大多数应用来说已经足够,足以容纳一个非常庞大的虚拟世界。因此,存储和渲染的挑战在于如何高效地组织和管理这些瓦片地图。
-
页面化和瓦片加载:
- 进一步提出了“分页”概念,即将整个地图分割为多个“页面”,每个页面包含一定数量的瓦片。当需要渲染某个区域时,只加载该区域的页面,而不是整个地图。这种方式可以显著降低内存使用,并允许在较低的硬件条件下运行大规模地图。
-
瓦片地图的显示:
- 最后,提到在显示时,屏幕会显示一个窗口,该窗口对应的是多个瓦片地图的拼接。瓦片地图本身不会直接显示在屏幕上,而是根据需要加载和呈现特定区域。这种设计可以支持更大的地图而不必一次性加载全部数据。
总结来说,这段讨论集中于如何通过位操作和虚拟内存技术,结合页面化管理的方式,有效地处理和显示大型游戏地图。通过这种方式,可以在不占用过多内存的前提下,支持大规模的地图和更高效的渲染。
通过位操作将瓦片地图中的坐标(x 和 y)打包成一个 32 位整数。具体来说:
低位(低位部分)用来存储瓦片的 索引,即瓦片在地图中的位置。
高位(高位部分)用来存储瓦片的 “页”,也就是瓦片属于哪个部分或区域。这个“页”通常指的是地图的一个大块区域,帮助高效管理和定位瓦片。
这种方式可以将 x 和 y 的坐标合并成一个 32 位值,便于更高效地在内存中存储和访问。
分辨率无关的瓷砖相对位置
-
转换瓦片位置:
目前的目标是将瓦片的相对位置进行压缩,并转换为更“数学友好”的表示形式。这些表示将是分辨率独立的,并且将转化为世界单位的形式,而不再依赖于像素。这意味着瓦片的位置将不再受限于屏幕分辨率,而是通过一个更通用的单位来处理,确保在不同的分辨率下,地图的行为是相同的。 -
虚拟索引系统:
瓦片的坐标系统将被压缩成一个统一的格式,使其可以作为一个虚拟的索引指向一个巨大的瓦片库。这个瓦片库将存储地图的所有数据。通过这种方式,可以创建一个更加清晰和干净的系统,便于后续的管理和访问。这个系统将允许通过虚拟索引来获取瓦片数据,而不需要直接操作原始像素位置。 -
减少像素依赖:
一旦完成这些转换,就不再需要考虑与像素相关的问题了。地图将变得更加独立于分辨率,所有操作和存储将基于世界单位或虚拟索引。这样做的目的是让地图系统更加通用,不依赖于特定的显示分辨率,同时也能简化后续的开发和维护。 -
提升效率:
通过这种转换方式,系统可以更加高效地管理瓦片。原本复杂的像素坐标将被统一为一种更加简洁、数学化的表示,从而让开发者能够更方便地处理地图数据。这种做法将提升系统的性能,使其在处理大规模地图时更加高效。 -
未来步骤:
明天的工作将专注于实现虚拟瓦片商店的构建,并开始进行瓦片数据的循环获取。在这个新的系统中,瓦片将根据需要从虚拟商店中拉取,而不再需要直接操作每个具体的瓦片地图。这个新方案可以有效地管理大量的地图数据,并优化资源的使用。
总结来说,这段内容描述了将瓦片地图中的位置坐标从像素单位转换为世界单位,并通过虚拟索引系统进行压缩,以简化存储和计算。通过这种方式,地图的管理将变得更加高效和灵活。
我们会做一个地图编辑器吗?
制作地图编辑器并不是因为游戏是程序生成的,而是基于其他原因。通过这种方式,强调了地图编辑器和程序生成的游戏设计之间的区别。具体来说,如果游戏的世界是通过程序生成的,那么传统意义上的“地图编辑器”可能不再适用,因为程序化生成的内容并不依赖于人工设计的地图。
你刚才提到的“快速 floor”是指什么?
_mm_floor_ps
是一个在 Intel 的 SSE (Streaming SIMD Extensions) 指令集中的函数,用于对单精度浮点数数组(__m128
类型)中的每个元素执行向下取整操作(即地板函数)。它对数组中的每个浮点值取不大于该值的最大整数,并将结果存入新的 __m128
寄存器。
函数原型:
__m128 _mm_floor_ps(__m128 a);
参数:
a
: 一个__m128
类型的值,包含四个单精度浮点数。
返回值:
- 返回一个
__m128
类型的寄存器,包含对输入浮点数组每个元素应用向下取整后的结果。
示例代码:
#include <xmmintrin.h>
#include <stdio.h>
int main() {
// 初始化一个包含四个浮点数的 __m128 向量
__m128 values = _mm_set_ps(3.7f, 2.8f, 1.5f, 4.9f);
// 执行向下取整操作
__m128 result = _mm_floor_ps(values);
// 将结果输出
float* res = (float*)&result;
printf("Floor results: %f, %f, %f, %f\n", res[0], res[1], res[2], res[3]);
return 0;
}
运行结果:
Floor results: 3.000000, 2.000000, 1.000000, 4.000000
说明:
__m128
数据类型可以存储四个单精度浮点数,这意味着_mm_floor_ps
会一次处理四个浮点数。这样可以加速在大数据集上的运算,尤其在需要并行处理时非常有用。- 如果你想要对更多的浮点数数组进行类似操作,使用 SIMD 指令集的这种方法会比逐个元素处理更加高效。
这种指令特别适合在处理图形学、物理模拟、信号处理等应用中,快速对大量数据进行相同数学操作。
是否足够多人有 SSE4?(注:根据 Steam 硬件调查,77%的人有 SSE4,99.8%的人有 SSE3)
_mm_round_ps
是一个在 Intel SSE (Streaming SIMD Extensions) 指令集中的函数,用于对 __m128
类型的单精度浮点数数组中的每个元素执行舍入操作。与 _mm_floor_ps
只是执行“向下取整”不同,_mm_round_ps
提供了多种舍入模式,可以执行多种舍入操作,类似于标准的四舍五入、向下取整、向上取整等。
函数原型:
__m128 _mm_round_ps(__m128 a, int rounding);
参数:
a
: 一个__m128
类型的值,包含四个单精度浮点数。rounding
: 一个整数,用于指定舍入模式,通常是以下几种舍入模式之一(使用位掩码表示):_MM_FROUND_TO_NEAREST_INT
: 向最近的整数舍入。_MM_FROUND_TO_ZERO
: 向零舍入(截断)。_MM_FROUND_TO_NEG_INF
: 向负无穷舍入(向下)。_MM_FROUND_TO_POS_INF
: 向正无穷舍入(向上)。
返回值:
- 返回一个
__m128
类型的寄存器,包含对输入的每个浮点数应用舍入后的结果。
示例代码:
#include <xmmintrin.h>
#include <stdio.h>
int main() {
// 初始化一个包含四个浮点数的 __m128 向量
__m128 values = _mm_set_ps(3.7f, 2.8f, 1.5f, 4.9f);
// 执行四舍五入(向最近的整数)
__m128 rounded = _mm_round_ps(values, _MM_FROUND_TO_NEAREST_INT);
// 打印结果
float* res = (float*)&rounded;
printf("Rounded results (nearest): %f, %f, %f, %f\n", res[0], res[1], res[2], res[3]);
return 0;
}
运行结果:
Rounded results (nearest): 4.000000, 3.000000, 2.000000, 5.000000
舍入模式详解:
_MM_FROUND_TO_NEAREST_INT
: 将数值舍入到最接近的整数,通常是四舍五入。_MM_FROUND_TO_ZERO
: 将数值舍入到零,也可以理解为截断。_MM_FROUND_TO_NEG_INF
: 将数值舍入到负无穷,即向下取整。_MM_FROUND_TO_POS_INF
: 将数值舍入到正无穷,即向上取整。
总结:
_mm_round_ps
提供了一个更为灵活的舍入机制,允许根据需要选择不同的舍入策略。它可以高效地在 SIMD 环境下处理多个数据点,使得对大量浮点数的舍入操作变得更快速和并行化。
关于加速 floor
的建议(/fp:fast
)
内容讨论了与存储和优化相关的设计决策,特别是在处理大规模数据或生成过程中的“递归异常”以及如何有效存储和重新生成世界。以下是对内容的详细总结:
-
递归异常与存储策略:
讨论了是否应该只存储更改(增量存储),而不是将整个世界的数据都存储下来。通过这种方式,可以从一个种子值重新生成世界的其他部分,而无需完全存储每个细节。这种方法类似于 Minecraft 等游戏的处理方式,其中只存储被改变的块,而不是整个世界。用户离开区域时,只需重新生成相关部分,减少存储需求。 -
创造性与世界生成:
提到了一些对生成世界的担忧。虽然增量存储和重新生成数据的方法节省了存储空间,但它可能会导致生成的世界显得单一、无聊,因为每个区域的生成都依赖于种子值。为了创造更具创意的世界,作者认为这种方法可能不足够灵活,因为它会限制生成的多样性和复杂性。 -
性能考虑:
对于不存储所有数据、仅在需要时重新生成的方案,表达了对其性能优化的疑虑。虽然这种方式理论上可以节省空间,但如果用户频繁更改世界内容,最终可能会回到完全存储所有数据的情况。因此,增量存储的方法并不总是有效,尤其是在用户对世界进行大量修改时,性能提升的效果并不明显。 -
浮动点计算与编译器优化:
讨论了如何利用编译器优化浮点计算的效率,避免不必要的计算或舍入操作。提到了一些优化方法,如通过放宽计算规则以获得更快速的结果,虽然这可能会牺牲部分准确性。最终的目标是确保编译器能生成更合适、更高效的代码。 -
具体的代码优化:
介绍了一些实际的优化过程,例如如何通过快速浮点计算实现“快速地板(floor)”操作。通过某些编译器指令或技巧,能够避免多余的操作,优化计算速度。 -
进一步的优化探索:
虽然已经取得了一些进展,但仍然有进一步优化的空间。具体来说,有时需要手动调整或测试代码,来确认哪些优化措施最为有效。 -
对特殊情况的处理:
在浮点数运算中,有一些特殊的情况需要处理,如非常规的数值或特殊的数值状态(如无穷大、NaN等)。尽管这些特殊情况可能影响计算的准确性,但在特定的环境下,这些不准确的计算可能是可以接受的。
/fp:fast
是一个编译器选项,通常在 Microsoft Visual C++ 编译器(MSVC)中使用,它允许编译器对浮点运算进行优化,重点是在性能而不是精度上。使用这个选项时,编译器会尽量加速浮点计算,可能会牺牲一些精度和符合标准的行为。
主要特点:
-
性能优先:启用
/fp:fast
后,编译器会尝试通过一些优化手段来提高浮点运算的速度。例如,它可能会禁用某些浮点精度检查,使用更快但不完全精确的浮点运算。 -
可能的精度损失:为了提升性能,编译器可能会忽略某些浮点标准中的要求,比如浮点运算的精确舍入规则、异常处理(如溢出、下溢等)以及 NaN(非数值)和无穷大的处理。这样做可能导致结果的精度不如在严格标准下的运算。
-
适用于特殊情况:这种优化非常适用于不要求严格浮点精度的应用场景,比如实时渲染或某些游戏引擎中的计算。在这些情况下,性能通常比精度更为重要。
-
禁用浮点一致性:启用
/fp:fast
后,编译器可能会禁用一些浮点运算的标准一致性要求,这意味着在不同硬件平台上,程序的浮点结果可能会有所不同。
示例:
如果你在命令行编译 C++ 程序时使用 /fp:fast
,它可能会使浮点计算更快,但你应该确保你的应用程序对浮点精度的容忍度较高,或者在性能上有较为明确的需求。
使用场景:
- 图形和游戏开发:某些图形计算或实时物理引擎可能会使用
fp:fast
来确保更高的帧率和更流畅的体验。 - 科学计算中的容忍误差的算法:如果算法可以容忍一定的浮点误差,使用
fp:fast
也可以提升效率。
需要注意的是,启用 /fp:fast
时,开发者应当确保其应用程序不会受到精度问题的严重影响,特别是当涉及到需要高精度的计算时。
为什么你要写 Result = foo(); return Result;
?
-
调试方便性:通过将计算结果存储在一个变量中,而不是直接返回,调试过程中可以方便地查看中间值。这在某些调试工具中尤为重要,因为调试器可能无法直接显示返回值,尤其是在某些环境下(如没有高级调试功能的平台)。有了这个中间变量,可以在调试窗口中轻松监视计算结果。
-
优化无损:虽然多余的中间变量可能看起来不必要,但它不会带来额外的计算开销,因为现代编译器会优化掉这些不必要的步骤。编译器能够识别出这些变量并在编译时将其优化掉,确保没有额外的性能损失。
-
确保返回值可见:通过这种方法,即使在调试器无法直接显示返回值时,程序员也能确保能看到最终计算的结果,提供了额外的便利和透明度。
-
代码的可读性和可维护性:使用中间变量(如
result
)可以使代码更清晰,特别是在涉及复杂计算或多步骤处理时。即使在没有调试的情况下,这样的做法也使得代码的意图更加明确。
对于真实32位坐标,使用瓷砖相对坐标比瓷砖页相对坐标更好吗?
在讨论“瓦片”(tile)和“页面”(page)相对位置时,主要涉及在地图或游戏开发中如何组织和处理元素的定位。这里的讨论焦点在于选择使用“瓦片相对坐标”还是“页面相对坐标”这种方法来处理元素的位置和布局。
-
瓦片相对和页面相对的比较:
- 使用“瓦片相对”的方式通常会保持统一性,因为每个瓦片的尺寸和位置在整个地图中是一致的。这种方式的优势在于它简化了布局和计算,因为每个瓦片的位置是固定的,可以在整个地图上按相同的方式处理。
- 相对页面的位置则可能需要处理页面边界,可能引发一些额外的复杂性。例如,如果地图的元素位于页面的边缘,可能会出现边界碰撞或其他布局问题。这种方式可能需要更多的考虑和处理,但它可能在某些情况下提供更好的灵活性。
-
瓦片相对更易于实现:
选择“瓦片相对”的方式可能更容易实现,因为它不需要处理页面边界的复杂性。瓦片的相对位置始终是统一的,无论地图的其他部分如何布局。这对于某些简单的地图系统来说,能够避免很多潜在的问题。 -
探索性开发和实践:
由于开发者没有直接写过类似的“地图游戏”,这些问题变成了探索性的实践。在没有确切经验的情况下,很难判断哪种方式会更好,或者哪种方法会提供更佳的结果。选择哪种方法还取决于开发过程中实际遇到的问题和需求。 -
灵活性与调整:
开发者认为如果使用某种方式(如页面相对)在实现过程中遇到问题,理论上是可以切换到另一种方法的。这种灵活性允许在开发过程中根据实际需要做出调整。
为什么不直接存储世界坐标的位置,然后通过除法/取余得到瓷砖信息?
在讨论为什么不直接存储玩家的坐标时,关键问题在于浮点数的精度和其在大规模世界地图中的限制。具体来说,存储单个浮点数值(例如玩家的x和y坐标)并不足够精确,尤其是在大范围的地图或虚拟世界中。
-
浮点精度不足:
- 浮点数(例如32位或64位浮点数)本身在存储精度上有限,特别是当涉及到非常大的世界坐标时。浮点数并不能精确地表示每个像素或每个小数位的坐标,这在大地图或需要高精度计算的场景中显得尤为重要。
-
存储和精度问题:
- 如果试图使用浮点数来存储非常精细的坐标(例如每个像素的256位精度),这种做法不仅会浪费大量存储空间,而且会因精度问题导致计算错误。对于包含大量瓦片地图的游戏,尤其是在跨越很大虚拟世界的情况下,单一浮点值无法满足精度需求。
-
大范围地图的挑战:
- 假设有数百或上千个瓦片地图在屏幕上展示,玩家的位置需要跨越这些地图的边界。使用单一浮点数来存储玩家坐标可能无法在这种大规模的环境下维持足够的精度。虽然现实中可能不会出现成千上万的地图瓦片,但目标是能够支持更大范围的世界,可能涉及成百上千的瓦片。
-
采用双级制系统:
- 为了克服浮点数精度的限制,采用双级制(Two-Tiered System)解决方案是一个常见的做法。这种方法通过更复杂的坐标系统(例如分层或分区模型)来存储玩家的位置。这样,能够通过多个层级的坐标数据来获取更高的精度,从而解决浮点数精度不足的问题。
-
解决方案的必要性:
- 通过这种双级制系统,可以获得比传统浮点坐标更多的精度,确保玩家在大规模世界中能够精准地定位,避免因精度不足导致的位置计算错误。对于复杂的游戏世界或虚拟环境,这样的解决方案是必不可少的。
总结来说,直接使用浮点数存储玩家的坐标在大规模世界地图中会遇到精度问题。为了解决这个问题,采用更复杂的双级制坐标系统,可以提供更高的精度,满足大范围地图的需求。
我们的稀疏性是否能更好地利用瓷砖页索引的前28位?
在讨论如何存储地图瓦片时,提到了一些关于坐标精度和存储优化的概念。主要讨论了如何在存储大规模地图时,合理利用每个位数的精度,以便能够高效表示瓦片位置和地图层次。
-
使用位表示瓦片:
- 讨论的关键之一是如何在地图中使用最后四个位来表示瓦片的坐标,而不是将这些位数用于其他用途。通过这种方法,可以有效地将瓦片位置映射到内存中。
-
稀疏地图的存储:
- 在计划中提到,由于城镇地图相对较稀疏,存储这些地图时并不需要占用太多的位。相较于地图中非常密集的区域,稀疏区域的存储可以减少不必要的资源消耗。使用这些位来表示瓦片而非其他信息,可以更合理地利用存储空间。
-
8x8瓦片的存储:
- 讨论中提到,每个瓦片地图块(例如8x8瓦片)可以通过一个地址来标识。对于这些瓦片,需要通过一定数量的位来表示其在地图中的位置,例如,3位可以用来表示瓦片的地址。通过这种方式,可以有效地存储地图上的每个瓦片的位置。
-
分层存储:
- 为了在存储瓦片时同时处理密集和稀疏的地图区域,采用了分层存储的方法。通过将瓦片的位置和地图的实际内容分开存储,可以实现更加灵活和高效的地图表示方式。尤其是对于稀疏的地图区域,存储方式将更加节省空间。
-
在稀疏存储中查找瓦片:
- 另一个提到的关键点是如何在稀疏地图存储中查找瓦片的地址。通过稀疏存储,只有在需要时才会存储瓦片信息,而不是存储整个地图的所有数据。这种方法能够显著降低内存使用,尤其是在地图较大的情况下。
-
整体方案:
- 总结来说,通过结合精确的位表示和分层存储,能够在大规模地图中高效管理瓦片信息。尤其是在面对大量瓦片数据时,采用稀疏存储和分层映射方案,可以避免不必要的存储浪费,并且提供灵活的地图加载方式。