我们在Cortex M0 +微控制器上创建了一个便携式平台游戏


引言


(本文末尾提供了到源代码和KiCAD项目的链接。)

尽管我们出生于8位时代,但我们的第一台计算机是Amiga500。这是一台出色的16位计算机,具有出色的图形和声音,非常适合游戏。 平台化已成为此计算机上非常流行的游戏类型。 它们中的许多色彩非常丰富,视差滚动也非常流畅。 这要归功于有才华的程序员,他们巧妙地使用了Amiga协处理器来增加屏幕颜色的数量。 以LionHeart为例!


Lionheart在Amiga上。 该静态图像不能传达图形的美感。

自90年代以来,电子技术发生了很大变化,现在有许多小型微控制器可让您创建令人惊奇的事物。

我们一直很喜欢平台游戏,而今天,只需花几美元,您就可以购买Raspberry Zero,安装Linux并“非常简单”地编写一个丰富多彩的平台游戏。

但这不是我们的任务-我们不想从大炮射麻雀!

我们希望使用内存有限的微控制器,而不是具有集成GPU的强大芯片系统! 换句话说,我们要困难!

顺便说一下,关于视频的可能性:有些人设法从他们项目中的AVR微控制器榨汁(例如,在Lft开发人员的Uzebox或Craft项目中)。 但是,为实现此目的,AVR微控制器迫使我们编写汇编程序,即使某些游戏非常出色,您也会遇到严重的限制,这些限制不允许您创建16位风格的游戏。

因此,我们决定使用更加平衡的微控制器/板,这使我们可以完全用C语言编写代码。

他不如Arduino Due强大,但不如Arduino Uno弱。 有趣的是,“到期”表示“两个”,而“雨诺”表示“一个”。 微软教我们正确计数(1、2、3、95、98,ME,2000,XP,Vista,7、8、10),而Arduino也是如此! 我们将使用Arduino Zero,它位于1到2之间的中间位置!

是的,根据Arduino,1 <0 <2。

特别是,我们对主板本身不感兴趣,但对它的处理器系列感兴趣。 Arduino Zero具有ATSAMD21系列微控制器,具有Cortex M0 +(48 MHz),256 KB闪存和32 KB RAM。

尽管48 MHz的Cortex M0 +在性能上明显优于旧的7 MHz的MC68000,但Amiga 500具有512 KB的RAM,硬件精灵,集成的双游戏板,Blitter(基于DMA的图像块传输引擎,内置了像素精确的碰撞识别系统)和透明度)和铜(光栅协处理器,可让您根据扫描位置对寄存器执行操作,以创建许多非常漂亮的效果)。 SAMD21没有所有这些硬件(与Blitter DMA相比非常简单的硬件除外),因此许多将以编程方式呈现。

我们要实现以下参数:

  • 在1.8英寸SPI显示器上的分辨率为160 x 128像素。
  • 每像素16位的图形;
  • 最高帧速率。 在12 MHz SPI时至少为25 fps,在24 MHz时为40 fps;
  • 视差滚动的双重游戏环境;
  • 一切都用C编写。没有汇编代码;
  • 像素精确的碰撞识别;
  • 屏幕覆盖。

看来实现这些目标非常困难。 特别是如果我们拒绝在asm上的代码!

例如,对于16位彩色,屏幕尺寸为160×128像素将需要40 KB的屏幕缓冲区,但是我们只有32 KB的RAM! 而且我们仍然需要在至少25/40 fps的频率下在双游戏场上进行视差滚动等等。

但是对我们来说,没有什么是不可能的,对吧?

我们使用ATSAMD21的技巧和内置功能! 作为“硬件”,我们采用uChip ,可以在Itaca商店中购买


uChip:我们项目的核心!

它具有与Arduino Zero相同的特性,但是要便宜得多,并且比原始的Arduino Zero便宜(是的,您可以在AliExpress上以10美元的价格购买假的Arduino Zero ...但是我们想在原始版本上构建)。 这将使我们能够创建一个小型的便携式控制台。 您几乎可以毫不费力地为Arduino Zero调整该项目,仅结果会非常麻烦。

我们还创建了一个小型测试板,为穷人实现了便携式控制台。 详情如下!


我们不会使用Arduino框架。 在优化和管理设备方面,它不太适合。 (而且,我们不要谈论IDE!)

在本文中,我们将描述如何进入游戏的最终版本,并描述所使用的所有优化和标准。 游戏本身尚未完成,缺少声音,关卡等。 但是,它可以用作许多不同类型游戏的起点!

此外,即使没有汇编程序,还有更多优化选项!

因此,让我们开始旅程吧!

难点


实际上,该项目有两个复杂的方面:时序和内存(RAM和存储)。

记忆


让我们从内存开始。 首先,我们使用切片来代替存储大量图片。 实际上,如果仔细分析大多数平台程序,您会注意到它们是由少量重复多次的图形元素(平铺)创建的。


Turrican 2在Amiga上。 有史以来最好的平台游戏之一。 您可以轻松地看到其中的瓷砖!

由于瓷砖的各种组合,世界/级别似乎是多种多样的。 这样可以节省驱动器上的大量内存,但不能解决巨大的帧缓冲区的问题。

由于uC相当大的计算能力和DMA的存在,我们使用的第二个技巧是可能的! 而不是将所有帧数据存储在RAM中(为什么需要这样做?),我们将从头开始在每个帧中创建一个场景。 特别是,我们将继续使用缓冲区,但要使它们适合高度为16像素的数据图形的一个水平块。

时间-CPU


当工程师需要创建某些东西时,他首先检查是否可行。 当然,我们在一开始就进行了该测试!

因此,在160×128像素的屏幕上,我们至少需要25 fps。 即512,000像素/秒。 由于微控制器的工作频率为48 MHz,因此每个像素至少有93个时钟周期。 如果我们以40 fps为目标,则该值下降到58个周期。

实际上,我们的微控制器一次最多可以处理2个像素,因为每个像素占用16位,而ATSAMD21具有32位内部总线,也就是说,性能会更好!

93个时钟周期的值告诉我们该任务是完全可行的! 实际上,我们可以得出结论,仅CPU一项就可以处理所有渲染任务,而无需DMA。 这很可能是真的,尤其是在使用汇编程序时。 但是,代码将很难处理。 在C语言中,它必须非常优化! 实际上,Cortex M0 +对C的友好程度不如Cortex M3,并且缺少很多指令(它甚至没有加载/保存后续的/初步的递增/递减!),而必须使用两条或更多条简单的指令来实现。

让我们看看绘制两个游戏场需要做些什么(假设我们已经知道x和y坐标,等等)。

  • 计算前景像素在闪存中的位置。
  • 获取像素值。
  • 如果它是透明的,则计算闪光灯中背景像素的位置。
  • 获取像素值。
  • 计算目标位置。
  • 将像素保存到缓冲区。

此外,对于可以进入缓冲区的每个子画面,应执行以下操作:

  • 计算精灵像素在闪存中的位置。
  • 获取像素值。
  • 如果它不是透明的,则计算目标缓冲区的位置。
  • 将像素保存在缓冲区中。

所有这些操作不仅不能实现为单个ASM指令,而且访问RAM /闪存时,每个ASM指令都需要两个周期。

此外,我们仍然没有游戏逻辑(幸运的是,由于每帧只计算一次,因此会花费少量时间),冲突识别,缓冲区处理以及通过SPI发送数据所需的指令。

例如,这是我们要做的伪代码(目前,我们假定游戏没有滚动,并且游戏场地具有恒定的颜色背景!)仅用于前景。

令cameraY和cameraX为游戏世界中显示器左上角的坐标。

令xTilepos和yTilepos为当前图块在地图上的位置。

xTilepos = cameraX / 16; // this is a rightward shift of 4 bits. yTilepos = cameraY / 16; destBufferAddress = &buffer[0][0]; for tile = 0...9 nTile = gameMap[yTilepos][xTilepos]; tileDataAddress = &tileData[nTile]; xTilepos = xTilepos + 1; for y = 0…15 for x = 0…15 pixel = *tileDataAddress; tileDataAddress = tileDataAddress + 1; *destBufferAddress = pixel; destBufferAddress = destBufferAddress + 1; next destBufferAddress = destBufferAddress + 144; // point to next row next destBufferAddress = destBufferAddress – ( 160 * 16 - 16); // now point to the position where the next tile will be saved. next 

2560像素(160 x 16)的指令数量约为16k,即 每个像素6个。 实际上,您一次可以绘制两个像素。 这会将每个像素的实际指令数减半,即每个像素的高级指令数大约为3。但是,这些高级指令中的一些将被分为两个或更多的汇编程序指令,或者至少需要两个周期才能完成,因为它们访问到内存。 同样,我们也没有考虑由于闪存的跳转和等待状态而重置CPU管道。 是的,我们距离58-93周期还差得很远,但是我们仍然需要考虑比赛场地和精灵的背景。

尽管我们看到问题可以在一个CPU上解决,但是DMA会快得多。 直接内存访问为屏幕精灵或更好的图形效果留出了更大的空间(例如,我们可以实现Alpha混合)。

我们将看到为每个图块配置DMA,我们需要少于100个C指令,即每个像素少于0.5个指令! 当然,DMA将仍然必须在内存中执行相同数量的传输,但是地址的增加和传输无需CPU的干预即可完成,CPU可以做其他事情(例如,计算和渲染精灵)。

使用SysTick计时器,我们发现为整个模块准备DMA,然后完成DMA所需的时间约为12k个时钟周期。 注意:时钟周期! 没有高水平的指示! 仅2560个像素,即 1,280个32位字。 实际上,每个32位字大约有10个周期。 但是,您需要考虑准备DMA所需的时间,以及DMA从RAM加载传输描述符所花费的时间(它实质上包含指针和已传输的字节数)。 此外,总会发生某种类型的内存总线更改(这样,CPU就不会在没有数据的情况下处于空闲状态),并且闪存需要至少一个等待状态。

时序-SPI


另一个瓶颈是SPI。 12 MHz足够25 fps吗? 答案是肯定的:12 MHz相当于每秒约36帧。 如果我们使用24 MHz,则限制将增加一倍!

顺便说一下,显示器和微控制器的规格说最大SPI速度分别是15和12 MHz。 我们进行了测试,并确保至少在我们需要的“方向”(微控制器写入显示屏)上可以毫无问题地将其提高到24 MHz。

我们将使用流行的1.8英寸SPI显示器。 我们确保ILI9163和ST7735都可以在12 MHz的频率下正常工作(至少在12 MHz下。已验证ST7735可以在最高24 MHz的频率下工作)。 如果要使用与教程“如何在Arduino Uno上播放视频”中的显示相同的显示,我们建议您对其进行修改,以防将来要添加SD支持。 我们使用的是SD卡版本,因此我们有很多空间可容纳其他元素,例如声音或其他级别。

图形


如前所述,游戏使用图块。 每个级别将由根据该表重复的图块组成,我们称为“ gameMap”。 每个磁贴有多大? 每个图块的大小会极大地影响内存消耗,细节和灵活性(并且,正如我们稍后将看到的,速度)。 太大的图块将需要为我们需要的每个小变化创建一个新的图块。 这将占用驱动器上的大量空间。


两个大小为32×32像素的图块(左侧和中央),只是一小部分(像素的右上部分为16×16)不同。 因此,我们需要存储两个大小为32×32像素的不同图块。 如果我们使用16×16像素的图块(在右侧),则仅需要存储两个16×16图块(全白图块和右侧的图块)。 但是,使用16×16的图块时,我们得到4个地图元素。

但是,每个屏幕所需的图块较少,这会提高速度(请参见下文)并减小每个级别的地图大小(即表格中的行和列数)。 瓷砖太小会产生相反的问题。 映射表越来越大,速度越来越慢。 当然,我们不会做出愚蠢的决定。 例如,选择大小为17×31像素的图块。 我们忠实的朋友-二度! 16×16的大小几乎是“黄金法则”,许多游戏中都使用它,我们会选择它!

我们的屏幕尺寸为160×128。 换句话说,每个屏幕需要10×8的图块,即 表中有80个条目。 对于10×10的大屏幕(或100×1的屏幕),只需要8,000条记录(如果我们使用16位进行记录,则需要16 KB。稍后我们将说明为什么我们决定选择16位进行记录)。

将此与整个屏幕上大图片可能占用的内存量进行比较:40 KB * 100 = 4 MB! 太疯狂了!

让我们谈谈渲染系统。

每个框架应包含(按绘制顺序):

  • 背景图形(运动场)
  • 级别图本身(前景)。
  • 精灵
  • 文字/顶部覆盖。

特别是,我们将依次执行以下操作:

  1. 绘图背景+前景(平铺)
  2. 绘制半透明瓷砖+子图形+顶部覆盖
  3. 通过SPI发送数据。

DMA将绘制背景和完全不透明的图块。 完全不透明的图块是其中没有透明像素的图块。


部分透明的图块(左)和完全不透明的图(右)。 在部分透明的图块中,一些像素(在左下方)是透明的,因此通过该区域可见背景。

DMA无法有效地渲染部分透明的图块,子画面和叠加层。 实际上,ATSAMD21芯片DMA系统只是复制数据,与Amiga计算机的Blitter不同,它不检查透明度(由颜色值设置)。 所有部分透明的元素均由CPU绘制。


然后使用DMA将数据传输到显示器。

创建管道


如您所见,如果我们在一个缓冲区中顺序执行这些操作,则将花费大量时间。 实际上,在DMA运行时,除了等待DMA完成之外,CPU不会忙! 这是实现图形引擎的不好方法。 此外,当DMA将数据发送到SPI设备时,它不会使用其全部带宽。 实际上,即使SPI以24 MHz的频率运行,数据也只能以3 MHz的频率传输,这是非常小的。 换句话说,DMA无法充分发挥其潜力:DMA可以执行其他任务,而不会真正降低性能。

这就是为什么我们实现流水线的原因,这是双缓冲(我们使用三个缓冲区!)思想的发展。 当然,最后,操作总是按顺序执行。 但是CPU和DMA同时执行不同的任务,而不会(特别是)相互影响。

这是同时发生的情况:

  • 该缓冲区用于通过DMA 1通道绘制背景数据。
  • 在另一个缓冲区(之前已填充了背景数据)中,CPU绘制了精灵和部分透明的图块。
  • 然后,另一个缓冲区(包含完整的水平数据块)用于通过DMA通道0通过SPI将数据发送到显示器。当然,用于SPI发送数据的缓冲区先前已被精灵填充,而SPI发送了前一个块,而另一个缓冲区充满瓷砖。



DMA


ATSAMD21芯片DMA系统不能与Blitter相提并论,但是它具有自己的有用功能。 多亏了DMA,尽管有双重竞争环境,我们仍可以提供很高的刷新率。

DMA传输的配置存储在RAM中的“ DMA描述符”中,告诉DMA如何以及在何处执行当前传输。 这些描述符可以连接在一起:如果存在连接(即不存在空指针),则在传输完成后,DMA将自动接收下一个描述符。 通过使用多个描述符,DMA可以执行“复杂传输”,这在例如源缓冲区是连续字节的非连续段序列时非常有用。 但是,获取和写入描述符会花费一些时间,因为您需要从RAM中保存/加载16字节的描述符。

DMA可以处理不同长度的数据:字节,半字(16位)和字(32位)。 在规范中,此长度称为“拍子大小”。 对于SPI,我们被迫使用字节传输(尽管当前的REVD规范指出ATSAMD21 SERCOM芯片具有FIFO,根据Microchip的说法,它可以接受32位数据,实际上,它们似乎没有FIFO。REVD规范还提到了SERCOM CTRLC寄存器在头文件和寄存器描述部分都没有。幸运的是,与AVR不同,ATSAMD21至少具有一个缓冲的传输数据寄存器,因此传输不会暂停!)。 当然,要绘制图块,我们使用32位。 这使您可以在每个节拍中复制两个像素。 ATSAMD21 DMA芯片还允许每个源拍子以固定数量的拍子大小增加源或目标地址。

这两个方面非常重要,它们决定了我们绘制瓷砖的方式。

首先,如果每个节拍渲染一个像素(16位),我们的系统吞吐量将减半。 我们不能拒绝全部带宽!

但是,如果我们每个节拍绘制两个像素,则游戏场将只能滚动偶数个像素,这将导致平滑移动。 要处理此问题,可以使用大于两个或更多像素的缓冲区。 在将数据发送到显示器时,我们将使用正确的偏移量(0或1像素),具体取决于我们需要将“相机”移动偶数还是奇数像素。

但是,为简单起见,我们保留11个完整图块(160 + 16像素)的空间,而不保留160 + 2像素的空间。 这种方法的一大优势是:我们不必计算和更新每个DMA描述符的收件人地址(这将需要数条指令,这可能会导致每个图块进行过多的计算)。 当然,我们将只绘制最小数量的像素,即不超过162。是的,最后,我们将花费一点额外的内存(考虑到三个缓冲区,这大约是1500个字节),以提高速度和简化性。 您还可以执行进一步的优化。


在此GIF动画中,所有16行块缓冲区(无描述符)都是可见的。 右边是实际显示的内容。 前32帧以GIF格式显示,在每帧上我们向右移动1个像素。 缓冲区的黑色区域是未更新的部分,其内容仅保留先前的操作。 当屏幕滚动奇数个帧时,缓冲区中将绘制一个162像素宽的区域。 但是,它们的第一列和最后一列(在动画中突出显示)都将被丢弃。 当滚动值为16像素的倍数时,缓冲区中的绘制操作从第一列开始(x = 0)。

垂直滚动呢?

在展示了一种将切片存储在闪存中的方法之后,我们将对其进行处理。

如何存放瓷砖


一个幼稚的方法(如果仅通过CPU进行渲染将适合我们)是将图块作为像素颜色序列存储在闪存中。 第一行的第一个像素,第二行,依此类推,直到第十六个像素。 然后,我们保存第二行的第一个像素,第二行,依此类推。

为什么这样的决定幼稚? 因为在这种情况下,每个DMA描述符DMA只能渲染16个像素! 因此,我们将需要16个描述符,每个描述符需要4 + 4个内存访问操作(即,传输32个字节-8个内存读取操作+ 8个内存写入操作-DMA必须再执行4个读取+ 4个写入)。 这是相当低效的!

实际上,对于每个描述符,DMA只能将源地址和目标地址增加固定数量的字。 将图块的第一行复制到缓冲区后,收件人地址不应增加1个字,而应增加一个指向缓冲区下一行的值。 这是不可能的,因为每个传输描述符仅指示拍子传输增量,无法更改。

将瓦片的每一行的前两个像素(即第0行的像素0和1,第1行的像素0和1,依此类推)依次发送到第15行的像素0和1,会更聪明。 0,依此类推。


如何存储图块?

在上图中,每个数字表示16位像素在图块阵列中的存储顺序。

可以使用描述符来完成,但是我们需要两件事:

  • 应该存储图块,以便在将源增加一个字时,我们始终指向正确的像素位置。 换句话说,如果(r,c)是r行和c列中的一个像素,那么我们需要依次保存像素(0,0)(0,1)(1,0)(1,1)(2,0) (2.1)...(15.0)(15.1)(0.2)(0.3)(1.2)(1.3)...
  • 缓冲区应为256像素宽(不是160)

第一个目标很容易实现:只需更改数据顺序,就可以在将图形导出到文件c时执行此操作(请参见上图)。

第二个问题可以解决,因为DMA允许您在每次拍后增加512字节来增加收件人地址。 这有两个结果:

  • 我们无法在SPI块上使用单个描述符发送数据。 这不是一个很严重的问题,因为最后我们通过160个像素读取了一个描述符。 对性能的影响将最小。
  • 该块的大小必须为256 * 2 * 16字节= 8 KB,并且其中将有很多“未使用的空间”。

但是,该空间仍然可以用于例如描述符。

实际上,每个描述符的大小为16个字节。 我们至少需要10 * 8(实际上是11 * 8!)的描述符用于图块,并需要16个描述符用于SPI。

这就是为什么瓷砖越多,速度越高的原因。 实际上,例如,如果我们使用32 x 32的图块,则每个屏幕需要更少的描述符(320个而不是640个)。 这将减少资源浪费。

显示数据块


块缓冲区,描述符和其他数据以结构类型存储,我们称为displayBlock_t。

displayBlock是由16个displayLineData_t元素组成的数组。 DisplayLine数据包含176个像素和80个字。 在这80个字中,我们存储显示描述符或其他有用的显示数据(使用并集)。



由于我们有16行,因此X位置的每个图块都使用X行的前8个DMA描述符(0到7),由于我们最多有11个图块(显示线为176像素宽),因此图块仅使用第一个DMA描述符11行数据。 所有行的描述符8–9和第11–15行的描述符0–9是空闲的。

其中,第0..7行的描述符8和9将用于SPI。

描述符0..9第11-15行(最多50个描述符,尽管我们仅使用其中的48个)将用于背景运动场。

下图显示了它们的结构。


背景运动场


背景运动场的处理方式有所不同。 首先,如果需要平滑滚动,则必须返回两像素格式,因为前景和背景将以不同的速度滚动。 因此,节拍将进行一半。 尽管这在速度方面是不利的,但此方法有助于集成。 我们只剩下少量的描述符,因此无法使用小图块。 另外,为了简化工作并快速添加视差,我们将使用较长的“扇区”。

仅当存在至少一个部分透明的像素时才绘制背景。 这意味着,如果只有一个透明磁贴,则将绘制背景。 当然,这是浪费带宽,但是却简化了一切。

比较背景和前面的运动场:

  • 在背景中,使用扇区,这些扇区是以“天真”的方式存储的长块。
  • 背景有自己的地图,但水平重复。 因此,可以使用更少的内存。
  • 每个扇区的背景都有视差。

前场


据说,每个块中最多有11个图块(10个完整图块或9个完整图块和2个部分文件)。 这些图块中的每个图块(如果未标记为透明的)都会绘制DMA。 如果不是完全不透明,则将其添加到列表中,稍后将在渲染精灵时对其进行分析。

我们将两个运动场连接在一起


背景运动场(总是计算)和前运动场的描述符形成一个很长的链表。 第一部分画了一个背景运动场。 第二部分在背景上绘制图块。 第二部分的长度可能是可变的,因为部分透明图块的DMA描述符已从列表中排除。 如果该块仅包含不透明切片,则DMA的配置如下。 直接从第一个图块的第一个描述符开始。

带有透明性的精灵和瓷砖


具有透明度和精灵的图块的处理几乎相同。进行图块/精灵像素分析。如果是黑色,则它是透明的,因此背景图块不会更改。如果不是黑色,则将背景像素替换为sprite / tile像素。

垂直滚动


使用水平滚动时,我们最多绘制11个图块,即使绘制11个图块,第一个和最后一个也仅被部分绘制。由于每个描述符都绘制图块的两列,因此可以实现部分渲染,因此我们可以轻松设置链接列表的开始和结束。

使用垂直滚动时,我们需要计算接收器寄存器和传输量。每帧必须设置几次。为避免这种麻烦,我们每帧最多可以绘制9个完整的块(如果滚动是16的倍数,则为8)。

配套设备


正如我们所说,系统的核心是uChip。其余的呢?

这是一个图!它的某些方面值得一提。


按键


为了优化I / O的使用,我们使用了一些技巧。我们将有4条传感器总线L1-L4和一条普通LC线。1和0交替施加到公共线上,因此,传感器总线将在内部上拉电阻的帮助下交替向下或向上拉。两个密钥连接在每个密钥总线和公共总线之间。与这两个键串联插入一个二极管。这些二极管中的每一个都以相反的方向切换,因此每次“读”一次键。

由于没有内置的键盘控制器(也没有内置的键盘控制器使用此有趣的方法),因此在每个帧的开头会快速轮询八个键。由于必须将输入上下拉动,因此我们不能(也不希望)使用外部电阻,因此我们需要使用集成电阻,该电阻可以具有相当高的电阻(60 kOhm)。这意味着当公共总线改变状态,并且数据总线改变其上/下拉状态时,您需要插入一些延迟,以便内置的上/下上拉电阻器改变合同并将杂散电容设置为所需的水平。但是我们不想等待!因此,我们将公共总线置于高阻抗状态(这样就不会有分歧),首先将传感器总线更改为逻辑值1或0,临时将它们配置为输出。之后,通过上拉或下拉将它们配置为输入。由于输出电阻约为数十欧姆,因此状态会在几纳秒内变化,也就是说,当传感器总线切换回输入时,它将已经处于所需状态。此后,公共总线切换到极性相反的输出。

这极大地提高了扫描速度,并且消除了延迟延迟/指令的需求。

SPI连接


我们连接了SD和显示器,以便它们彼此通信,而无需将数据传输到ATSAMD21。如果您要播放视频,这可能会很有用。

连接MISO和MOSI的电阻应低。如果它们太大,则SPI将无法工作,因为信号将太弱。

优化和进一步发展


最大的问题之一是RAM的使用。三个块每个占用8 KB,每个堆栈和其他变量仅剩下8 KB。目前,我们只有1.3 KB的可用RAM + 4 KB的堆栈(每个堆栈4 KB-这很多,也许我们会减少它)。

但是,您可以使用高度不是16而是8像素的块。这将增加DMA描述符上的资源浪费,但几乎使块缓冲区占用的内存量减半(请注意,如果继续使用16×16瓦片,描述符的数量将不会更改,因此我们将不得不更改块的结构)。这样可以释放大约7.5 KB的RAM,这对于实现功能(例如带有机密的可修改卡或添加声音)非常有用(尽管即使1 KB RAM也可以添加声音)。

另一个问题是sprite,但此修改要容易得多,并且只需要createNextFrameScene()函数即可。实际上,我们正在RAM中创建一个包含所有精灵状态的巨大数组。然后,对于每个精灵,我们计算其位置是否在屏幕区域内,然后对其进行动画处理并将其添加到渲染列表中。

相反,您可以执行优化。例如,在gameMap中,您不仅可以存储图块的值,还可以存储在编辑器中设置的指示图块透明度的标志。这将使我们能够快速检查是否应渲染图块:DMA还是CPU。这就是为什么我们为磁贴卡使用16位记录的原因。如果我们假设我们有一组256个图块(目前少于128个图块,但是闪存上有足够的空间来添加新的图块),则有7个可用位可用于其他目的。这七个位中的三个可用于指示是否存储了一个精灵/对象。例如:

0b000 =
0b001 =
0b010 =
0b011 =
0b100 =
0b101 =
0b110 =
0b111 = , , .


然后,您可以在RAM中创建一个位表,其中的每个位都表示是否检测到了(例如,敌人)/是否拾取了(例如,奖励)/是否激活了某个对象(切换)。在10×10屏幕的水平上,这将需要8000位,即 1 KB的RAM。当检测到敌人或获得奖励时,重置该位。

在createNextFrameScene()中,我们必须检查与当前可见区域中的图块相对应的位。如果它们的值为1:

  • 如果这是一个奖励,那么只需将其添加到精灵列表即可进行渲染。
  • 如果这是一个敌人,则创建一个动态精灵并重置标志。在下一帧中,场景将包含动态精灵,直到敌人离开屏幕或被杀死为止。

这种方法有缺点。

  1. -, ( ). .
  2. -, 80 , , . , 32 . , «/» ( «», .. 0!). «», «» ( ).
  3. -, . ( ), . , .
  4. -, , , , . , , . , , , , !
  5. , (, Unreal Tournament , ).

但是,通过这种方式,我们可以更加高效地存储和处理精灵。

但是,此技术与“游戏逻辑”比与游戏的图形引擎更相关。

也许将来我们将实现此功能。

总结一下


我们希望您喜欢这篇介绍性文章。我们需要解释更多方面,这将是以后文章的主题。

同时,您可以下载游戏的完整源代码!如果您喜欢,则可以在经济上支持艺术家ansimuz,后者绘制了所有图形并将其免费提供给全世界。我们也接受捐款

游戏尚未结束。我们想要添加声音,许多级别,可以与之交互的对象等等。您可以创建自己的修改!我们希望看到具有新图形和水平的新游戏!

不久我们将发布一个地图编辑器,但是现在向社区展示它太基本了!

录影带


(注意:由于光线不足,以较低的帧频记录了视频!很快我们将更新视频,以便您可以估计40 fps的全速!)


感激之情


游戏的图形(以及某些图像上显示的图块)取自ansimuz创建免费资产“ Sunny Land”

可下载资料


该项目的源代码是公共领域的,也就是说,它是免费提供的。我们分享它,希望它对某人有用。我们不保证由于代码中的任何错误/错误而不会造成任何问题!KiCad 项目

原理图 Atmel Studio 7项目(源)



Source: https://habr.com/ru/post/zh-CN466323/


All Articles