在像素背后:Starduino - 3D在28KB中的游戏

2021-06-24 06:06:07

我正在寻找一个有趣的业余工程,在找到Arduboy时磨练我的AVR装配技能,这是一个坚固的“Arduino”,我可以留在口袋里。

已经有很多有趣的游戏,甚至是3D的一些,但没有填充多边形游戏?这必须纠正。

Arduboy是一个与Arduino IDE兼容的便携式游戏机,使用与Arduino Leonardo相同的微控制器,这是一个Atmega32U4,但与Leonardo板不同,它配备了所有在一个漂亮的坚固的套管中预先安装的硬件。

Atmega32U4包含一个8位CPU,一点RAM,一些用于程序和数据的Flash ROM,一个微小的EEPROM,用于在电源熄灭时保存数据,并具有一堆GPIO引脚来连接到外部硬件。

显示屏是128×64 1BPP,这意味着完整的帧占用了我们的存储器的1KB。除非我们在飞行中生成显示器(对于基于多边形的3D游戏并不实用,否则我尝试过。更稍后,我们稍后更多地为1.5KB的RAM留下了我们的所有工作数据,包括CPU堆栈。

它通过单向SPI连接到MCU,我们不会从OLED控制器获得V-SYNC信号,因为该特定显示制造商没有暴露在带状电缆上的信号。 < jackie_chan_wtf.jpg>

没有音频生成电路。对于每个PWM脉冲,必须通过IRQ在软件中生成声音。在我们珍贵的CPU时间吃饭。

2018年半月后,当我收到我的单位时,我从一个填充多边形光栅化器开始看,看它是否可以以可玩的速度获得任何东西。

然后我开始尝试不同的游戏概念。要查看哪些风格将更好地符合OLED显示器,发动机的性能,ROM空间,有限的控制等。

我在Snes上的任天堂明星狐狸安顿下一个概念。

在MCU硬件上调试绝不是与您最喜欢的IDE中最喜欢的工作站上的方便,与边界检查器和日志文件等所有漂亮的工具。

因此,我将项目从Arduino IDE中移动到我自己的基于Makefile的构建系统中,使用本机PC端口,因此我可以在PC上进行多大测试,而无需每次编程闪存。

它还让我做一些方便的节省时间,例如在级别预览窗口中显示边界框和Bezier路径,并直播级别和声音。

如果您正在计划花费几个月的业余时间,在游戏投资时间开始让您的生活更轻松。不花很长时间才能回报。

走出arduino IDE让我扔掉USB库和其他一些作品。但这意味着我无法访问USB串行端口进行调试...

默认情况下,显示控制器处于“水平模式”,这对于2D闪烁很好,但对于光栅化多边形并不方便。

幸运的是,控制器具有一个“垂直寻址模式”,它将内存映射转换为熟悉和光栅化的64×128屏幕(侧向),从左上角,右转,使用LSB在最左侧像素上启动的8×128字节(0x01 =最左边,0x80 =右边)。

静态const8_t init_cmds [] progmem = {0xae,0xa8,64-1,0xd3,0x00,0x40,0xDA,0x14,0xC8,0xDA,0x12,0x22,0xDA,0xD9,0x22,0xDB,0x00,0x2,0xDB,0x00,0x2E,0xA6,0xA4 ,0xAF,0x20,0x01,0xA0};

制作3D引擎的伟大事物是您真正关心屏幕的方式。您可以在投影矩阵中修复此功能。

我不会进入光栅化多边形的细节,因为已经有很多关于这两者在线和死树格式的书籍和教程。选择你最喜欢的。

只要纹理不涉及CPU就可以处理它。 32份注册它在这份工作中非常宽朴。

然而,我用了天空盒的伎俩。通过将地平线投影到屏幕的边缘,实际上,它实际上是在屏幕上以偏斜的方式在屏幕上绘制的动态产生的2×96纹理,以确定高度和斜率。

在清除帧缓冲器之前,将表示地面的线绘制到背景图案上。它们的位置根据投影矩阵计算。

ROM空间在这里是溢价如此明显的数据需要以某种方式压缩。我们也没有太多的RAM工作,所以一切都必须被压缩(或生成!)。

并且必须在保存的数据和解压缩器代码之间击中平衡。 CPU上的每个指令都是2个字节,这可以快速加起来。

我不想通过手动进入顶点值来模拟3D对象,因此我使一个obj到C ++转换器 - 压缩机工具,并在Modo中建模了对象。

保存字节的第一个明显的位置是尽可能镜像模型,因此压缩机在所有3维(x,y,z)上寻找匹配的镜像多边形,并将它们合并为镜像批次。

这意味着我所需要的只是3位,以便如果必须重复在x,y和/或z上的多批处理镜像的多批处理,则告诉解压缩器。

发动机使用32位签名定点数学以16.16格式(16位分数)。这意味着每个顶点12个字节,但这太浪费了。

我使用的诀窍是将顶点编码为4位的有效量和一个共享的4位指数,每个顶点总共2个字节。

现代GPU使用类似的技巧来编码HDR颜色,例如32位OpenGL中的RGB9_E5_EXT。

void vectorunpack(固定* out_v,const uint16_t * v){uint16_t t = pgm_read_word_nword_near(v);字节e =(t&amp; 0xf)+ 8; INT32_T * DST =(INT32_T *)OUT_V; for(字节i = 3; i - ;){int8_t tx =(t&amp; 0xf0); Tx&gt;&gt; = 4; * DST ++ =((INT32_T)TX)<&lt;&lt; e; t&gt;&gt; = 4; }}

这是3D模型被压缩机工具屠宰的地方,但是通过仔细调整顶点,即使在舍入之后也可以让模型看起来正常。乞丐不能选择。

每个Poly都有一个4位“颜色”索引,进入16个条目调色板,该调色板映射到9个不同的抖动模式。间接允许我创建脉冲亮度动画和闪烁。

通常,3D引擎中的动画是使用骨矩阵变换与复杂的Bezier曲线进行完成,但这对于这个项目来说将太大,并且在任何合理的时间框架中,差的小CPU无法处理多个骨矩阵。

因此,使用简单的顶点插值来完成动画。当顶点仅为2个字节(并镜像!),对于将获得动画的几个网格,它变得更加合理。

所有对象都具有缩放值以重用网格,边界框隐式1x1x1以围绕对象的原点为中心。 (-0.5到0.5)然后通过实例的缩放缩放。

您认为额外的隐形对象将添加比存储边界框更多的字节,但处理多个尺寸框的额外代码复杂性,每个对象的多个界限和每个对象的位置递减节省。

游戏包含一个介绍级别,其中逐渐引入元素以让新玩家学习控件。

当游戏开始时,RNG只能选择每个类别的前几个片段。

随机数发生器以常数播种,因此每次都会产生相同的水平以保持高分展平。

第一次将字形突出插入8×5缓冲液(记住屏幕在其侧面)。

样式缓冲区告诉文本呈现例程如何呈现多个文本通行证:

它似乎是浪费,但黑色轮廓步骤会照顾到黑色,所以第二次通过只需要反转。

为了帮助控制游戏时,当帧速率倾斜时,输入的读取60次(作为游戏播放逻辑的速率相同)并存储到循环缓冲器中,因此在其输入时间处理输入。

50%的时间花费了几何变换,剪切另外30%。

我不得不写16.16 x 16.16 - &gt; 16.16装配中的固定点乘法代码获得体面的帧速率。

除了没有做出可笑的溢出32 x 32 - &gt的主要优点。在C ++中的64个定点数学是我能够跳过一些长乘法步骤作为3D顶点*矩阵变换有很多零字节的档案舍入的方式,所以汇编代码跳过整个(字节* 4字节)为每个零字节乘以累积代码的块。

最快的例程是没有任何东西的例程。这是速度最大的增长。

下载.arduboy文件(98kb turbo版本)(经典版本:.arduboy文件(100kb))在您的arduboy上闪存游戏。 或者具有可打印PDF指令小册子(600kb)的完整.zip文件并包含使用AVRDUDE或您的首选方法上传的.hex文件。