在本教程中,我正在探索MKRIoTCarrier作为游戏机的功能。Carrier拥有您所需的一切,还有一些您需要一个合适的游戏机。所需材料一个非常酷的圆形彩色显示器,宽256像素。五个触摸按钮。五个RGBLED。压电蜂鸣器。一个3轴加速度计。其他一些我不会在这里使用的东西。该名称包括IoT(物联网),但运营商不包含用于网络通信的硬件。你必须选择一个合适的微控制器板来连接到互联网,fiArduinoMKRWiFi1010。在这个项目中,我使用的是ArduinoMKR1000,但我不会进入游戏开发的物联网方面。阅读此说明后,您应该对如何为ArduinoMKRIoTCarrier编写游戏有了很好的了解。或者你可以直接使用我的代码并进一步开发它。新尝试对于这个教学,我设计了一个新游戏。BreakIn是一款Breakout游戏,游戏中的世界已被彻底颠覆。桨叶沿着显示屏边缘以圆形路径无休止地转动。通过倾斜设备来控制桨。要打破的瓷砖在中间呈六角形图案,即蜂窝状。圆形显示器和圆形设备(如MKRIoTCarrier)的完美游戏。快速跳转到游戏如果您有ArduinoMKRIoTCarrier和Arduino板并且只想玩游戏,请滚动到“代码,将游戏上传到您的设备”一章以下载游戏并开始玩。事实上,你真的应该这样做。在那之后,阅读这个指导会更有意义。本教程中的章节游戏物理和数学碰撞几何蜂窝位图使用16位颜色创建和使用精灵行动中的精灵关于声音的一些事情编写文本元素简单的菜单代码,将游戏上传到您的设备探索游戏设计概念加速度计作为游戏控制16位彩色图形精灵基于像素的碰撞检测声音菜单处理补给品ArduinoMKR物联网载体ArduinoMKR1000(或任何具有MKR1010外形的Arduino)ArduinoIDE一块3.7V锂聚合物电池GIMP用于将图像转换为C代码的在线服务第1步:游戏物理和数学屏幕逻辑上是一个256x256像素的平方彩色显示器。物理上它是球形的,球心的逻辑坐标是128,128。如果你习惯于在屏幕上定位,左上角像素是0,0,开始认为没有左上角需要时间角落。根本没有角落。将注意力集中在128、128的中间位置。球棒蝙蝠有长度。它是一条简单的线,一个像素宽,起点和终点在一个圆上,中间点在128、128、半径为110。蝙蝠的位置是由倾斜的角度决定的设备。蝙蝠的起点和终点坐标计算如下:lx1=ox+cos(aold-0.2)*110;ly1=oy-sin(aold-0.2)*110;lx2=ox+cos(aold+0.2)*110;ly2=oy-sin(aold+0.2)*110;...其中ox和oy是屏幕中点(128,128),aold是倾斜的角度,0.2是蝙蝠角大小的一半(图像中的蓝色部分)。倾斜角度只有当设备不水平时才能定义倾斜角度。MKRIoTCarrier的加速度计测量三个轴,x、y和z。当您注视显示屏时,x轴向右,y轴向上,z轴与您的视线方向一致。我们对x和y感兴趣。当设备水平时,x和y都为零,无法计算角度。当设备倾斜时,x和y都会发生变化。角度计算如下:载体.IMUmodule.readAcceleration(x,y,z);h2=x*x+y*y;//倾斜幅度的平方值if(h2>0.000001)//如果有最轻微的倾斜,我们可以继续{a=atan2(y,x);//a是倾斜的角度delta=a-aold;//将它与之前计算的倾斜度进行比较如果(delta如果(delta>3.1415926536)delta-=6.28318530718;h=sqrt(h2);//倾斜幅度的“线性”值如果(h>1)h=1;aold+=h*delta;//将旧值更改为最后测量的倾斜度这是一个基本的过滤器,可以使蝙蝠移动得比较平稳。如果想要另一种行为,需要调整一些变量。现在,蝙蝠速度非常快,没有惯性动量。蜂窝和球一组矩形的瓷砖在圆形屏幕上看起来会很单调,所以我创建了六边形蜂窝。每个细胞都是一种球形。计算每个像素颜色以提供球形外观。由于六边形几何形状,每一行都有不同的球体位图。偶数行(0,2,4...)有4*4像素球,奇数行有5*5像素。由于分辨率和正确的像素颜色,所有看起来都一样。有61个单元格。球以每个游戏循环0.76像素的速度移动。球位图为3*3像素。碰撞检测如下。对于球的每个新位置,检查球的角像素是否会放置在不等于黑色的像素上。如果是这样,蜂窝中的一个细胞就会被击中。找出它是哪个单元格并获得单元格的坐标-即单元格的中心。5*5像素单元具有明确的中心像素。但是4*4单元格以“子像素”为中心。子像素在这里没问题,因为球本身在子像素上有一个坐标。球的坐标和速度都是浮点值。只有在绘制时,它们才会四舍五入到最近的像素。碰撞检测可能是关于计算球和每个单元格之间的距离,这将导致更准确的弹跳几何形状。但只是检查像素是一种方式快点。球可以同时击中两个单元格-至少在仅检查像素时是这样。如果是这种情况,请计算两个单元格之间的平均点并使球与其碰撞。同样,一个非常快速和肮脏的技巧来避免超级复杂的计算。所以球有速度和坐标。它击中具有坐标的单元格。从这两个坐标我们得到一条直线和一条直线的法线。在下一步中,我将描述弹跳几何。第2步:碰撞几何球与细胞我们这里有很多向量算术。n是碰撞时通过单元格和球的线。t是通过碰撞点到n的垂线。v̅是球的速度向量。n̅是方向为n、长度为1的向量(单位向量)。v̅ₙ是球从t定义的“墙”反弹后的新速度向量。弹跳是这样的:球以速度v̅接近。在碰撞时刻,线t被定义为穿过碰撞点的碰撞球体的切线。法线n定义为垂直于t。我们需要找到球的新速度v̅ₙ。从这个页面我们了解到v̅ₙ=v̅-2*(v̅·n̅)n̅,其中v̅·n̅是v̅和n̅的点积,其中n̅是长度为1的向量,从单元格到线的方向到球。在代码中,它看起来像这样:ny=bally-coly;//colx,coly是碰撞单元的中心nx=ballx-colx;//nx,ny是从单元格到球的向量l=sqrt(nx*nx+ny*ny);nx/=l;ny/=l;//现在它的长度是1dotp=2.*(vx*nx+vy*ny);//双点积nx*=dotp;ny*=dotp;vx-=nx;vy-=ny;//现在我们已经根据法线反射了速度球与蝙蝠当球接近球棒时,我们计算从球到球棒每个端点的向量的叉积。叉积被定义为垂直于“交叉”的两个向量的向量(天哪,我喜欢以完全非专业的方式讲数学)。当球在球棒的右侧时,结果向量朝一个方向移动。恰好在碰撞时,结果向量为零。当球经过球棒时,结果向量向另一个方向移动。在我们的例子中,我们检查结果向量的z分量,当球越过球棒时,它从-到+。不,我们的目标不是碰撞发生的确切时刻。想想蝙蝠有点弹性。代码如下所示://检查球与蝙蝠if((quarantene>5)&&(lx1-ballx)*(ly2-bally)-(ly1-bally)*(lx2-ballx)>0)//叉积{//如果叉积为正,我们已经越过了蝙蝠线隔离是一个计数器。当发生击球时,它会归零。之后它在每个游戏循环中递增。当球没有立即从球棒的错误一侧逃脱时,整个球棒碰撞控制被置于quarantene5个循环中,以避免烦人的重复击球。到目前为止,我们只知道球是否已经过线。我们想知道我们是击中了球棒还是没有击中球棒。如果我们击打球棒(球现在在球棒的另一侧),我们可以看到两个向量之间的角度超过90度。但是如果我们错过了蝙蝠,角度就很窄了。窄角(90度)导致负点积。因此:if((lx1-ballx)*(lx2-ballx)+(ly1-bally)*(ly2-bally)>0)//如果点积>0你错过了球棒{//重置新球();}else//你击中了球棒{//立即弹跳浮动nx、ny、l、dotp;ny=lx2-lx1;nx=ly1-ly2;l=sqrt(nx*nx+ny*ny);nx/=l;ny/=l;dotp=2.*(vx*nx+vy*ny);nx*=dotp;ny*=dotp;vx-=nx;vy-=ny;隔离期=0;//将球放入隔离区5个游戏循环以避免卡住}游戏循环有两个地方,其中一个球被错过。一,如果球与中间的距离超过128-球仍可能“在球棒的右侧”。另一个,当球没有击中球棒时,即使它仍然在屏幕上。两者都需要,取决于我们如何放置蝙蝠。现在蝙蝠与中间的距离是110像素。所以如果球距离115像素,它显然没有击中球棒,但我们希望在游戏停止寻找新球之前看到它。第3步:蜂窝位图MKRIoTCarrier带有一个基于Adafruit图形的库。如果不深入挖掘和调整库,就无法从Carrier的圆形屏幕读取像素数据。好吧,至少getPixel()不可用。但是可用的是GFXcanvas16结构。它是一个位图,其中为每个像素保留一个16位字(两个字节)。这样一个位图,宽度为44,高度为40,定义为:GFXcanvas16中心(44,40);现在,您可以使用与在屏幕上绘制时相同的功能绘制此位图。但是您也可以读取每个像素以检查其颜色。要将我们的蜂窝位图绘制到屏幕上,我们执行以下操作:载体.display.drawRGBBitmap(ox-21,oy-19,center.getBuffer(),44,40);位图左上角离屏幕中心-21,-19。当我们想知道球是否击中了蜂窝中的一个单元格时,我们不是从屏幕上读取像素,而我们不能,而是从蜂窝位图中读取相应的像素。我们检查球的每个角。如果角坐标在绘制蜂窝的矩形内,我们从位图中读取相应的x,y像素,如下所示:如果(center.getPixel(x,y)!=0){//好的,我们发生了碰撞。处理它。冲突意味着必须删除单元格。知道我们碰撞了哪个像素,我们就知道要删除哪个单元格。我们只需在其上绘制一个黑色矩形,然后再次将位图绘制到屏幕上:fy=y/9;mfy=y%9;if(mfy{fx=x/5;*coly+=9*fy+1.5;*colx+=5*fx+1.5;center.fillRect(5*fx,9*fy,4,4,黑色);}else//第1、3、5行...{fx=(x-2)/5;*coly+=9*fy+6;*colx+=5*fx+4;center.fillRect(5*fx+2,9*fy+4,5,5,黑色);}...载体.display.drawRGBBitmap(ox-21,oy-19,center.getBuffer(),44,40);第4步:使用16位颜色彩色显示器可以将每个像素设置为任何颜色。几乎。它混合了一定数量的红色、绿色和蓝色,以实现尽可能多的颜色。MKRIoTCarrier有一个16位彩色显示器,这意味着一个16位字描述了每个像素的颜色。最低值为0,即黑色。最高值为65535,为白色。我们的十进制数字不能很好地揭示每个值是什么颜色。作为十六进制单词,我们得到黑色的0x0000和白色的0xFFF。出于某种原因,颜色被定义为十六进制字,如下所示:#define黑色0x0000#define蓝色0x001F#define红色0xF800#define绿色0x07E0#defineCYAN0x07FF#define洋红色0xF81F#define黄色0xFFE0#define白色0xFFFF对于读者来说,十六进制单词的颜色仍然不是很明显。我的意思是,8B7D是什么颜色?但是,如果您擅长混合RGB颜色,则可以直接将颜色定义为二进制数。如果您不确定,请使用您最喜欢的图形编辑器,混合您的颜色并读取RGB值并将它们转换为红色(0-31)、绿色(0-63)和蓝色(0-31)值。我想要粉红色。我知道我会得到带有全红色的粉红色,并说75%的绿色和75%的蓝色。五位全红色是11111。六位75%绿色是110000。五位75%蓝色是11000。所以,把它们放在一起,我得到:#definePINK0b1111111000011000//为清晰起见划分:1111111000011000所以,一个字是四个十六进制数字。一个十六进制数字是四个二进制数位或四个位。这使得一个字有16位,因此是16位颜色。在代码中使用#defines并将颜色值写为二进制数,以提高可读性。它只会让你的代码文件更大,而不是最终的程序文件!如果您不熟悉二进制数,现在是学习的好时机!第5步:创建和使用Sprite精灵是您想要放置在屏幕上的图片元素。将元素放置在屏幕上时,屏幕可能已经有一些背景图像。你的新图片元素可能不是矩形图像,而是具有独特轮廓的卡通人物。典型的精灵仍然是矩形图像,但它可能包含透明部分以允许背景可见。因此,精灵的每个像素都由红色、绿色、蓝色和透明度的级别定义它有。在Carrier显示器的16位颜色系统(或者实际上是Carrier使用的Adafruit图形库)中,每个像素被定义为一个16位字。所有16位都用于红色、绿色和蓝色。有颜色格式,从绿色通道中窃取一位,留下绿色只有5位,就像红色和蓝色一样。这一位告诉像素是否透明。但是由于我们的库为绿色通道提供了6位,因此使用了另一种确定透明度的方法。透明度在屏幕像素中没有真正意义。它们在保存在内存中以在屏幕上相互重叠的图像中更有意义。蒙面精灵所以我们在位图中定义了一个16位全彩色图像。我们可以称之为主图像。为了能够分辨哪些部分是透明的,我们需要另一个具有相同大小但具有1位色深的图像。这个图像我们称之为蒙版图像。每个像素只有一位!0表示主图像中对应的像素是透明的,1表示像素是不透明的。在屏幕上绘制一个精灵是这样的:carrier.display.drawRGBBitmap(100,30,hccell,hccell_mask,26,29);在坐标100、30上,我们绘制了名为hccell的16位位图。我们使用名为hccell_mask的1位位图作为掩蔽位图。两个位图的大小均为26像素宽,29像素高。hccell命名为位图需要是一个16位的字阵列中的程序存储器。它将像这样声明:constuint16_thccell[]PROGMEM={0xefff,0x03ff,0x87ff,0x03ff,0x01ff,0x03fe,0x00ff,0x03f8,0x003f,0x03e0,0x001f,0x03c0,0x000f,0x0300,0x0001,0x0000,0x0001,0x0000,...};我们这里有一堆16位字。每个字定义位图中一个像素的颜色。名为hccell_mask的位图需要是程序存储器中的8位字节数组。它将像这样声明:constuint8_thccell_mask[]PROGMEM={0x00,0x1e,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0xff,0xe0,0x00,...};这里我们有一堆字节。每个字节是8位。每个字节定义8个像素,无论它们是透明的还是不透明的。据我所知,数组中的第一个字节在左上角定义了一个8像素的垂直行。下一个字节是右侧的下一个8像素垂直行。但我们真的不需要这个细节。PROGMEM指的是程序存储器。Adafruit图形库似乎允许将位图保存在程序内存或SRAM中,变量通常位于其中。程序存储器通常要大得多,因此图像通常放置在那里。其他库(如Arduboy)要求图像位于程序存储器中。当我们在代码中将图像定义为字或字节数组时,它们首先位于程序存储器中。如果没有关键字PROGMEM,程序启动时图像将被复制到SRAM。这可能会加快绘图过程,但不会节省程序内存。图片还在。我们需要将精灵创建为图像文件(BMP或PNG)的软件。我们还需要将图像文件转换为包含字或字节数组的C代码的软件。为了绘制精灵,我使用Gimp。为了创建16位图像数组,我使用了HenningKarlsen的在线工具ImageConverter,或者在这里找到它的可下载版本。为了创建8位掩码,我使用https://lvgl.io/tools/imageconverter。创作过程在您最喜欢的图形编辑器中设计您的精灵。您可能希望选择与游戏中相同的背景颜色。在我们的例子中它是黑色的。使用bmp格式以全彩色保存图像,这将恢复每个像素而不会损失质量。它必须是24位颜色,而不是32位颜色。使用颜色选择工具,选择黑色背景。如果工具有阈值,请将其设置为零。请注意,仅选择了完整的黑色像素。现在反转选择。现在你已经选择了你的精灵角色。用白色填充它。将此图像保存为您的蒙版。它可以是相同的24位颜色,即使它只有白色和黑色。接下来是将图像转换为c代码。使用Henning的ImageConverter我得到的ac文件看起来像这样:...#如果已定义(__AVR__)#include#elif定义(__PIC32MX__)#definePROGMEM#elif定义(__arm__)#definePROGMEM#万一constunsignedshortmysprite[4929]PROGMEM={0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x1,0000,00000000000000000000000000000000000x0000000000000000000x00x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x000,000000000000000000000000002)...我只想要以constunsignedshort开头并以}结尾的内容;那就是图像数据。每个元素是一个定义一个像素的16位字。我将这些东西复制粘贴到我的程序中。我喜欢把unsignedshort改成uint16_t,基本一样。并且不需要方括号内的数字(此处为4929)。空方括号就足够了。接下来,我对蒙版图像执行相同操作,但使用另一个工具。使用不同工具的原因是图像是全彩色的,带有16位字(uint16_t)的数组。掩码是一个带有字节数组(uint8_t)的1位图像。而且我还没有找到一种工具可以同时处理16位彩色图像和8位蒙版图像。因此,我使用https://lvgl.io/tools/imageconverter上的LVGL工具。我愿意:图像文件:我为我的精灵搜索蒙版图像名称:我将其命名为mysprite_mask以记住这将用作蒙版的图像颜色格式:索引2种颜色输出格式:C数组抖动:不要检查这个点击转换会给你这样的东西:...constLV_ATTRIBUTE_MEM_ALIGNLV_ATTRIBUTE_LARGE_CONSTLV_ATTRIBUTE_IMG_HC1_MASK.Cuint8_tmysprite_mask.c_map[]={0x42,0x47,0x52,0xff,/*索引0的颜色*/0x00,0x00,0x00,0xff,/*索引1的颜色*/0x00,0x00,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0x80,0x00,0x00,0x00,0x00,0x00,...这是一个索引的1位图像,每个字节定义8个像素。首先我们有四个字节告诉位为0的每个像素的颜色,然后四个字节用位1来表示每个像素的颜色。这两行我们不需要!我们只对数组的其余部分感兴趣。我们在代码中将数据复制到一个新的图像数据数组中,最好紧跟在我们之前创建的图像之后:constuint8_tmysprite_mask[]PROGMEM={为0x00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0×00,0x61,0X31,为0xC2,的0x6A,0xc1,的0x410x20用于0x08时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0×00,0x00,0x00,为0x00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0×00,0x60的,0x10的,0×02(0x73)的,0X24,0xc5,0×06,0xde,0xa6,0xcd,0x46,0x94,0xe2,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0,0,0,0,0,0,0x,0,0,0,0,0,0x,0,0,00x00,0x00,...为0x00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0×00,0x61,0X31,为0xE5,0×83,0x66,为0x9c,0x61,0X31,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00时,0×00,0x00,0x00,};请注意,此数据类型是uint8_t。要使用它,您需要在用于创建图像文件的图形编辑器中检查图像大小。或者您可能会在下载的掩码的c文件末尾找到数据:...constlv_img_dsc_thccell_mask.c={.header.always_zero=0,.header.w=96,.header.h=107,.data_size=1292,.header.cf=LV_IMG_CF_INDEXED_1BIT,.data=mysprite_mask.c_map,};它说宽度为96,高度为107。该代码片段是LVGL图形系统的一部分,我们在这里不使用。我们只使用从他们的转换器获得的数据。也就是说,LVGL可能是用于MKRIoTCarrier上所有图形的不错选择。所以我们有mysprite指向作为单词数组的彩色图像。我们有mysprite_mask指向精灵掩码,它是一个字节数组。然后我们这样做:carrier.display.drawRGBBitmap(100,30,mysprite,mysprite_mask,96,107);第6步:行动中的精灵所以我在开始屏幕中有这个由6个单元组成的蜂窝。并且使用单个精灵绘制单元格6次。其中一些被删除。由于单元格是六边形而不是矩形,因此必须将其绘制为蒙版精灵,因此它将适合六边形平铺图案。如果没有蒙版,每次我绘制单元格时,矩形的黑角都会覆盖图像中的先前内容。大黄蜂大黄蜂的形象比蜜蜂大。蜜蜂周围有大约5像素的黑色。这样我就可以让蜜蜂在屏幕上移动。每个新位置与前一个位置的距离不超过5个像素。精灵将覆盖它的踪迹。但是当蜜蜂撞到蜂巢时,黑色框架覆盖黄色细胞会看起来很丑。为此,我需要一个面具。向右移动时,蜜蜂有一个面具负责覆盖左侧的小径,但定义了右侧蜜蜂翅膀的轮廓。另外两个面具用于蜜蜂上下移动。开始画面视频中看到的开始屏幕序列经过以下过程:画标题把蜂窝里的细胞一一画出来动画蜜蜂击中一个单元格、删除单元格、发出哔哔声、弹跳通过点击两个单元格重复如果按键被按下,则中止。为了找出放置所有东西的坐标并计算每次蜜蜂飞行的开始和结束坐标,我将每个精灵放置在Gimp中一个256x256像素的图像中。我将精灵作为独立层移动并从那里读取关键坐标。蜜蜂精灵的大小是48*32。第一次飞行发生在坐标256,90到169,102之间。在飞行的每一帧中,蜜蜂飞行3个像素。所以我是这样做的://蜜蜂从右边飞进来for(inti=256;i>169;i-=3){载体.display.drawRGBBitmap(i,map(i,256,169,90,102),littlebee,littlebee_maskl,48,32);延迟(1);运营商.Buttons.update();如果(carrier.Button1.getTouch())返回1;如果(carrier.Button2.getTouch())返回2;}map()函数计算每个x坐标的y坐标。由于移动是从右到左,我使用位图littlebee_maskl作为掩码,当蜜蜂从右侧接近时,它将很好地放置在黄色单元格上。但是精灵会在右边缘涂上黑色。在循环过程中,我们不断检查按钮是否按下,这将结束开始屏幕。当蜜蜂到达位置169、102时,它覆盖了蜂窝最右边的单元格的一部分。此时,我们发出哔哔声并用以下命令擦除蜂窝:if(soundOn)carrier.Buzzer.sound(B4);载体.display.drawBitmap(HCX+54,HCY,hccell_mask,26,29,BLACK);蜂鸣声稍后静音。我们为绘制蜂窝单元而创建的hccell_mask会派上用场,当我们想要擦除它们时。我们简单地使用drawBitmap()函数,它简单地用函数调用中定义的颜色绘制一个单色位图。擦除单元格后,我们继续用新的方向和相应的掩码绘制飞蜂,以便正确擦除。又快又好为非常简单的设备(如Arduino和OLED显示器)编写游戏需要巧妙地使用精灵。事情发生需要时间。在开头绘制游戏名称显示图形例程有多慢。我们不能使用一种技术,即在屏幕外创建每一帧,然后为每一帧更新整个屏幕。相反,我们尝试使用尽可能小的精灵直接绘制到屏幕上,让精灵覆盖自己的轨迹。第7步:关于声音的一些事情MKRIoTCarrier中的声音是由一个简单的压电蜂鸣器产生的。这意味着可以产生简单的方波音调。好消息是,一旦您播放了一个音调,它就会在程序继续时继续发声。不好的是你必须自己关闭它。当然必须有带有功能的蜂鸣器库,可以在给定的时间内简单地启动蜂鸣声,而程序可以继续,并且中断计时器负责关闭蜂鸣声。有用于在游戏继续时播放旋律的库。我不会进入那个。毕竟,它只是一个压电蜂鸣器,很多人觉得在演奏时一直听蜂鸣器旋律很烦人。相反,我在某些游戏事件中使用单声蜂鸣声。当球击中球棒时,发出哔哔声。现在我重用了quarantene变量,它主要用于计算游戏循环以避免对球棒的双重打击。因为quarantene设置为零,所以在主游戏循环中我执行以下操作:隔离++;如果(隔离==4)载体.蜂鸣器.noSound();所以在击球后的第四个游戏循环中,我关闭了哔哔声。蜂鸣声的长度通过决定声音的游戏循环次数来调整。当球击中蜂窝中的一个细胞时,会发出哔哔声。哔声开始,quarantene设置为零。关闭蝙蝠声音的相同程序也会因此关闭细胞撞击声音。这实际上是糟糕的代码设计,我为三件事重用了相同的变量。从技术上讲,在击中一个单元格后的四个游戏循环中,球不能在球棒上弹跳。这没有问题,因为在球从蜂窝到达球棒之前需要数十个游戏循环。但这会造成麻烦,如果游戏设计将演变为多层次游戏,其中某些关卡具有特大的蜂窝。这里使用的另一个技巧是快速哔哔声的不同音高,当球进入蜂窝时,当角度正确时,这种情况偶尔会发生。单元格命中产生两种不同的蜂鸣频率,660和495。我这样做:boolsoundflip=true;uint16_t翻转[2]={660,495};//E5,B4这些是全局变量。然后,对于每个单元格命中,我执行以下操作:载体.蜂鸣器.sound(sflips[soundflip]);soundflip=!soundflip;隔离期=0;soundflip是一个布尔值,但用作包含两个频率的sflips数组的索引。并且将quarantene设置为零将注意在此之后的第四个游戏循环中蜂鸣声结束。现在我可以通过将quarantene设置为1、2或3来缩短哔哔声。同样,这是一个快速而肮脏的黑客来实现某些目标,绝不是良好的编码实践。关于使用频率我已经建立了一个音乐频率表:#defineB3248#defineC4264#defineCISS4280#defineD4297#defineDISS4313#defineE4330#defineF4352#defineFISS4373#defineG4396#defineGISS4417#defineA4440#defineAISS4467#defineB4495#defineC5528#defineCISS5560#defineD5594#defineDISS5626#defineE5660#defineF5704#defineFISS5746#defineG5792#defineGISS5834#defineA5880#defineAISS5934#defineB5990#defineC61056#defineCISS61120#defineD61188#defineDISS61252#defineE61320#defineF61408#defineFISS61492#defineG61584#defineGISS61668#defineA61760#defineAISS61868#defineB61980#defineC72112#defineZ1//休息#defineEND0//终止旋律数组我在这里使用瑞典语声调名称。C#变成了CISS。我首先定义了全音阶C、D、E、F、G、A和B的频率。我基于A4的频率为440Hz,并根据间隔计算了其余部分。CEG说,与常见的平均律相反,当在三和弦之后演奏几个音调时,只是音程可能听起来最好。然后我想我也需要半音,才能演奏更复杂的旋律。我应该放弃公正的语调,转而采用平等的气质。但是作为测试,我想我会添加半音阶音符作为每个全音阶之间的等阶。所以,如果C是264,D是297,我得到的C#是SQRT(264*297)=280。结果非常好。新球的倒计时使用B4和B5音调,这是倒计时中使用的典型八度跳跃(我儿子告诉我这可能源于某些超级马里奥游戏)。蝙蝠的声音是E4。细胞撞击声是E5和B4。使用的音高是“真正的音乐音调”,根据我的公正和平等的气质进行调整。为游戏声音选择任何随机频率只会导致烦人的噪音,没有人愿意听音乐。一个简单的旋律界面游戏以Rimsky-Korsakoff的TheFlightoftheBumbleBee中一段非常短的旋律片段开始。为此,我开发了一个简单的旋律播放函数,这使我可以将旋律编写为音调数组:uint16_tmain_theme[]={B4、AISS4、A4、GISS4、G4、C5、B4、AISS4、B4、END};音调数组使用我的#defined频率并以END宏终止。该函数将如下所示:voidplay_melody(uint16_t*mel){for(uint16_t*p=mel;*p!=END;p++){如果(*p!=Z)载体.蜂鸣器.声音(*p);延迟(70);//非常快的速度,例如mm=215bpm的16分音符载体.蜂鸣器.noSound();延迟(1);}}TheFlightoftheBumbleBee的另一个旋律片段是这样的:uint16_tnew_ball_melody[]={B4,Z,G4,Z,E4,Z,C4,Z,E4,Z,G4,B4,Z,END};第8步:编写文本元素游戏由一些用carrier.display.print()创建的文本元素组成。对于每张印刷品,我们可以定义颜色、尺寸和位置。当它发生在游戏过程中时,重要的是擦除之前的打印。我使用了一种技术,我将每个打印坐标存储在一个游戏循环中,以用于在下一个游戏循环中进行擦除。它是这样的://写入时间newtime=(millis()-gameTime)/1000;//以秒为单位计算使用时间timex=118-(ballx-128)*60/dist;//计算时间打印的坐标,timey=120-(bally-128)*60/dist;//它与球相反载体.display.setTextSize(2);载体.display.setTextColor(黑色);载体.display.setCursor(otimex,otimey);//在旧的时间用黑色写,载体.display.print(otime);//在旧坐标载体.display.setTextColor(白色);载体.display.setCursor(timex,timey);载体.display.print(newtime);//写入新的时间otimex=timex;//保存新的时间和坐标otimey=及时;//下一个游戏循环时间=新时间;类似的技术用于新球前的倒计时。计时器打印似乎有点闪烁。这可以避免,如果我们只在时间或坐标从前一个游戏循环中改变时才打印新时间。我们还可以调查是否可以使用给定的背景颜色进行打印。设置背景颜色可能会清除上一个游戏循环中的打印。第9步:设置快捷菜单我的菜单遵循蜂窝的想法。我们有三列。速度、重力和声音。每列定义一个设置。所以我们有三种球的速度,四种重力模式(在当前版本的游戏中没有实现)和三种声音模式(关闭,只有声音效果,声音效果和音乐)。您倾斜设备以移动光标(呈红色/ocra),然后按X按钮进行选择。第10步:代码,将游戏上传到您的设备在ArduinoIDE中创建一个新项目并立即保存并为其命名,fiBreakIn。如果您还没有安装MKRIoTCarrier的库,请安装它。为项目选择合适的电路板。您可能拥有MKR1000或MKR1010。将BreakIn01.cpp的内容粘贴到编辑器中。那将成为您的ino文件,即项目的主要代码文件。将bee.h、honeycombs.h和logo.h文件移动或复制到您的项目文件中。现在您应该准备好编译并将游戏上传到您的设备。BreakIn01.cpp下载蜜蜂.h下载蜂窝.h下载标志.h下载