曾经想知道编写自己的复古游戏需要多少工作? pong为arduino编写代码有多容易?和我一起,向我展示如何构建arduino供电的迷你复古游戏机,以及如何从头开始编写pong。最终结果如下:
构建计划
这是一个相当简单的电路。 电位器(电位器)将控制游戏,而arduino将会驱动oled显示屏。这将在面包板上生产,但是您可能希望将其制成永久性电路并将其安装在箱子中。之前我们已经写过有关重新创建pong的文章,但是今天我将向您展示如何从头开始编写代码,并分解每个部分。
您需要的内容
这是您需要的:
1 x arduino(任何型号)
1 x 10k电位器
1 x 0.96英寸i2c oled显示屏
1 x面包板
各种公头》公连接线
任何arduino都应该工作,请查看我们的购买指南如果您不确定要购买哪种型号。
这些oled显示器非常酷。通常可以购买白色,蓝色,黄色或这三种的混合物。它们确实是全彩色的,但是它们又增加了该项目的复杂性和成本。
电路
这是一个非常简单的电路。如果您对arduino没有太多的经验,请先查看这些初学者项目。
在这里是:
在锅的前面,将左引脚连接到 + 5v ,将右引脚连接到接地。将中间引脚连接到模拟引脚0 (a0)。
使用i2c协议连接oled显示器。将 vcc 和 gnd 连接到arduino + 5v 和接地。将 scl 连接到模拟五( a5 )。将 sda 连接到模拟4 ( a4 )。它连接到模拟引脚的原因很简单。这些引脚包含i2c协议所需的电路。确保它们正确连接,并且没有交叉。确切的引脚会因型号而异,但是nano和uno会使用a4和a5。如果您未使用arduino或nano,请查看模型的wire库文档。
电位器测试
上传此测试代码(请确保从中选择正确的电路板和端口工具》 面板和工具》 端口菜单):
void setup() {
// put your setup code here, to run once:
serial.begin(9600); // setup serial
}
void loop() {
// put your main code here, to run repeatedly:
serial.println(analogread(a0)); // print the value from the pot
delay(500);
}
现在打开串行监视器(右上》 串行监视器)并转动锅。您应该看到在串行监视器上显示的值。完全逆时针应为零,完全逆时针应为 1023 :
您稍后会对此进行调整,但现在就可以了。如果什么也没有发生,或者您不做任何事情就改变了值,请断开并仔细检查电路。
oled测试
oled显示的配置稍微复杂一些。您需要安装两个库才能首先驱动显示。从github下载adafruit_ssd1306和adafruit-gfx库。将文件复制到您的库文件夹中。这取决于您的操作系统:
mac os:/用户/用户名/documents/arduino/libraries
linux:/home/username/sketchbook
windows:/users/arduino/libraries
现在上传测试草图。转到文件》 示例》 adafruit ssd1306 》 ssd1306_128x64_i2c 。这应该给您一个包含大量图形的大草图:
如果上传后没有任何反应,请断开连接并再次检查您的连接。如果示例不在菜单中,则可能需要重新启动arduino ide。
代码
现在是时候编写代码了。我将解释每个步骤,所以如果您只想使其运行,请跳到最后。这是相当数量的代码,因此,如果您不确定,请查看以下10个免费资源以学习编码。
首先包括必要的库:
#include
#include
#include
#include
spi 和 wire 是用于处理i2c通信的两个arduino库。 adafruit_gfx 和 adafruit_ssd1306 是您先前安装的库。
下一步,配置显示:
adafruit_ssd1306 display(4);
然后设置运行游戏所需的所有变量:
int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};
const int pixel_size = 8, wall_width = 4, paddle_width = 4, ball_size = 4, speed = 3;
int playerscore = 0, aiscore = 0, playerpos = 0, aipos = 0;
char balldirectionhori = ‘r’, balldirectionverti = ‘s’;
boolean inprogress = true;
这些变量存储运行游戏所需的所有数据。其中一些存储球的位置,屏幕的大小,球员的位置等。请注意其中的一些是 const 的意思,它们是恒定的,并且永远不会改变。
屏幕分辨率和焊球位置存储在数组中。数组是相似事物的集合,对于球,存储坐标( x 和 y )。访问数组中的元素很容易(不要在文件中包含此代码):
resolution[1];
由于数组从零开始,因此将返回分辨率数组中的第二个元素( 64 )。更新元素甚至更容易(同样,不包含此代码):
ball[1] = 15;
在 void setup()内,配置显示:/p》 void setup() {
display.begin(ssd1306_switchcapvcc, 0x3c);
display.display();
}
第一行告诉adafruit库,您的显示器正在使用什么尺寸和通讯协议(在这种情况下,为 128 x 64 和 i2c )。第二行( display.display())告诉屏幕显示缓冲区中存储的内容(无内容)。
创建两个名为 drawball 和 eraseball :
void drawball(int x, int y) {
display.drawcircle(x, y, ball_size, white);
}
void eraseball(int x, int y) {
display.drawcircle(x, y, ball_size, black);
}
这些采用 x 和 y 坐标并使用显示库中的 drawcircle 方法将其绘制在屏幕上。这使用了前面定义的常量 ball_size 。尝试更改此设置,看看会发生什么。此drawcircle方法接受像素颜色-黑色或白色。因为这是单色显示(一种颜色),所以白色表示像素处于打开状态,黑色表示像素处于关闭状态。
现在创建一种称为 moveai 的方法:
void moveai() {
eraseaipaddle(aipos);
if (ball[1] 》 aipos) {
++aipos;
}
else if (ball[1] 《 aipos) {
--aipos;
}
drawaipaddle(aipos);
}
此方法处理移动人工智能或 ai 播放器。这是一个非常简单的计算机对手-如果球在桨上方,请向上移动。它在桨下面,向下移动。很简单,但是效果很好。增量和减量符号( ++ aipos 和 –aipos )用于从aiposition中添加或减去一个。您可以添加或减去更大的数字以使ai更快地移动,因此更难以克服。这样做的方法如下:
aipos += 2;
并且:
aipos -= 2;
加号等于和负号符号是aipos当前值加/减两个的简写。这是另一种方法:
aipos = aipos + 2;
和
aipos = aipos - 1;
注意此方法如何首先擦除桨,并且然后再次绘制。必须这样做。如果绘制了新的桨叶位置,则屏幕上将有两个重叠的桨叶。
drawnet 方法使用两个循环绘制球网:
void drawnet() {
for (int i = 0; i 《 (resolution[1] / wall_width); ++i) {
drawpixel(((resolution[0] / 2) - 1), i * (wall_width) + (wall_width * i), wall_width);
}
}
这将使用 wall_width 变量来设置其大小。
创建名为 drawpixels 和的方法擦除像素。就像球形方法一样,两者之间的唯一区别是像素的颜色:
void drawpixel(int posx, int posy, int dimensions) {
for (int x = 0; x 《 dimensions; ++x) {
for (int y = 0; y 《 dimensions; ++y) {
display.drawpixel((posx + x), (posy + y), white);
}
}
}
void erasepixel(int posx, int posy, int dimensions) {
for (int x = 0; x 《 dimensions; ++x) {
for (int y = 0; y 《 dimensions; ++y) {
display.drawpixel((posx + x), (posy + y), black);
}
}
}
再次,这两种方法都使用两个 》循环绘制一组像素。循环不必使用库 drawpixel 方法绘制每个像素,而是根据给定的尺寸绘制一组像素。
drawscore 方法使用库的文本功能将播放器和ai得分写入屏幕。这些存储在 playerscore 和 aiscore 中:
void drawscore() {
display.settextsize(2);
display.settextcolor(white);
display.setcursor(45, 0);
display.println(playerscore);
display.setcursor(75, 0);
display.println(aiscore);
}
此方法还具有 erasescore 对应,将像素设置为黑色或关闭。
最后四种方法非常相似。他们绘制并擦除了玩家和ai球拍:
void eraseplayerpaddle(int row) {
erasepixel(0, row - (paddle_width * 2), paddle_width);
erasepixel(0, row - paddle_width, paddle_width);
erasepixel(0, row, paddle_width);
erasepixel(0, row + paddle_width, paddle_width);
erasepixel(0, row + (paddle_width + 2), paddle_width);
}
注意他们如何调用之前创建的 erasepixel 方法。这些方法会绘制并擦除适当的桨。
主循环中还有更多逻辑。这是完整的代码:
#include
#include
#include
#include
adafruit_ssd1306 display(4);
int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};
const int pixel_size = 8, wall_width = 4, paddle_width = 4, ball_size = 4, speed = 3;
int playerscore = 0, aiscore = 0, playerpos = 0, aipos = 0;
char balldirectionhori = ‘r’, balldirectionverti = ‘s’;
boolean inprogress = true;
void setup() {
display.begin(ssd1306_switchcapvcc, 0x3c);
display.display();
}
void loop() {
if (aiscore 》 9 || playerscore 》 9) {
// check game state
inprogress = false;
}
if (inprogress) {
erasescore();
eraseball(ball[0], ball[1]);
if (balldirectionverti == ‘u’) {
// move ball up diagonally
ball[1] = ball[1] - speed;
}
if (balldirectionverti == ‘d’) {
// move ball down diagonally
ball[1] = ball[1] + speed;
}
if (ball[1] 《= 0) { // bounce the ball off the top balldirectionverti = ‘d’; } if (ball[1] 》= resolution[1]) {
// bounce the ball off the bottom
balldirectionverti = ‘u’;
}
if (balldirectionhori == ‘r’) {
ball[0] = ball[0] + speed; // move ball
if (ball[0] 》= (resolution[0] - 6)) {
// ball is at the ai edge of the screen
if ((aipos + 12) 》= ball[1] && (aipos - 12) 《= ball[1]) { // ball hits ai paddle if (ball[1] 》 (aipos + 4)) {
// deflect ball down
balldirectionverti = ‘d’;
}
else if (ball[1] 《 (aipos - 4)) {
// deflect ball up
balldirectionverti = ‘u’;
}
else {
// deflect ball straight
balldirectionverti = ‘s’;
}
// change ball direction
balldirectionhori = ‘l’;
}
else {
// goal!
ball[0] = 6; // move ball to other side of screen
balldirectionverti = ‘s’; // reset ball to straight travel
ball[1] = resolution[1] / 2; // move ball to middle of screen
++playerscore; // increase player score
}
}
}
if (balldirectionhori == ‘l’) {
ball[0] = ball[0] - speed; // move ball
if (ball[0] 《= 6) { // ball is at the player edge of the screen if ((playerpos + 12) 》= ball[1] && (playerpos - 12) 《= ball[1]) { // ball hits player paddle if (ball[1] 》 (playerpos + 4)) {
// deflect ball down
balldirectionverti = ‘d’;
}
else if (ball[1] 《 (playerpos - 4)) { // deflect ball up balldirectionverti = ‘u’; } else { // deflect ball straight balldirectionverti = ‘s’; } // change ball direction balldirectionhori = ‘r’; } else { ball[0] = resolution[0] - 6; // move ball to other side of screen balldirectionverti = ‘s’; // reset ball to straight travel ball[1] = resolution[1] / 2; // move ball to middle of screen ++aiscore; // increase ai score } } } drawball(ball[0], ball[1]); eraseplayerpaddle(playerpos); playerpos = analogread(a2); // read player potentiometer playerpos = map(playerpos, 0, 1023, 8, 54); // convert value from 0 - 1023 to 8 - 54 drawplayerpaddle(playerpos); moveai(); drawnet(); drawscore(); } else { // somebody has won display.cleardisplay(); display.settextsize(4); display.settextcolor(white); display.setcursor(0, 0); // figure out who if (aiscore 》 playerscore) {
display.println(“you lose!”);
}
else if (playerscore 》 aiscore) {
display.println(“you win!”);
}
}
display.display();
}
void moveai() {
// move the ai paddle
eraseaipaddle(aipos);
if (ball[1] 》 aipos) {
++aipos;
}
else if (ball[1] 《 aipos) {
--aipos;
}
drawaipaddle(aipos);
}
void drawscore() {
// draw ai and player scores
display.settextsize(2);
display.settextcolor(white);
display.setcursor(45, 0);
display.println(playerscore);
display.setcursor(75, 0);
display.println(aiscore);
}
void erasescore() {
// erase ai and player scores
display.settextsize(2);
display.settextcolor(black);
display.setcursor(45, 0);
display.println(playerscore);
display.setcursor(75, 0);
display.println(aiscore);
}
void drawnet() {
for (int i = 0; i 《 (resolution[1] / wall_width); ++i) {
drawpixel(((resolution[0] / 2) - 1), i * (wall_width) + (wall_width * i), wall_width);
}
}
void drawpixel(int posx, int posy, int dimensions) {
// draw group of pixels
for (int x = 0; x 《 dimensions; ++x) {
for (int y = 0; y 《 dimensions; ++y) {
display.drawpixel((posx + x), (posy + y), white);
}
}
}
void erasepixel(int posx, int posy, int dimensions) {
// erase group of pixels
for (int x = 0; x 《 dimensions; ++x) {
for (int y = 0; y 《 dimensions; ++y) {
display.drawpixel((posx + x), (posy + y), black);
}
}
}
void eraseplayerpaddle(int row) {
erasepixel(0, row - (paddle_width * 2), paddle_width);
erasepixel(0, row - paddle_width, paddle_width);
erasepixel(0, row, paddle_width);
erasepixel(0, row + paddle_width, paddle_width);
erasepixel(0, row + (paddle_width + 2), paddle_width);
}
void drawplayerpaddle(int row) {
drawpixel(0, row - (paddle_width * 2), paddle_width);
drawpixel(0, row - paddle_width, paddle_width);
drawpixel(0, row, paddle_width);
drawpixel(0, row + paddle_width, paddle_width);
drawpixel(0, row + (paddle_width + 2), paddle_width);
}
void drawaipaddle(int row) {
int column = resolution[0] - paddle_width;
drawpixel(column, row - (paddle_width * 2), paddle_width);
drawpixel(column, row - paddle_width, paddle_width);
drawpixel(column, row, paddle_width);
drawpixel(column, row + paddle_width, paddle_width);
drawpixel(column, row + (paddle_width * 2), paddle_width);
}
void eraseaipaddle(int row) {
int column = resolution[0] - paddle_width;
erasepixel(column, row - (paddle_width * 2), paddle_width);
erasepixel(column, row - paddle_width, paddle_width);
erasepixel(column, row, paddle_width);
erasepixel(column, row + paddle_width, paddle_width);
erasepixel(column, row + (paddle_width * 2), paddle_width);
}
void drawball(int x, int y) {
display.drawcircle(x, y, ball_size, white);
}
void eraseball(int x, int y) {
display.drawcircle(x, y, ball_size, black);
}
这是您最终得到的结果:
对代码很有信心,您可以进行许多修改:
添加难度级别菜单(更改ai和球速)。
向其中添加一些随机移动
为两个玩家添加另一个底池。
添加一个暂停按钮。
现在看看这些复古游戏pi zero项目。
裁员不能停!全球科技企业年内裁员21万,超去年全年
PLC信号接口技术文件怎么制作
长续航高颜值新贵物理指针智能手表!
16WiFi多个城市关停,城市公共WiFi也被看衰
大事件:深圳高交会惊现我国首例机器人伤人案
如何构建Arduino供电的迷你复古游戏机
交叉编译器安装教程
IIC-China 2011顶尖厂商深圳同台亮相
在变压器设计中,为什么要考虑短时过载能力?
Microchip发布符合C-Q100 0级要求,可在高温环
国内智能家居的起点在哪?智能家居未来将走向何处
张晓论币:以太坊完成最终升级,为何要换共识机制?
搭载骁龙870 5G移动平台的realme真我Q5 Pro
关于雷克萨斯多级全混动技术分析
电源模块在使用过程中需要注意哪些问题?
生物传感的发展历程、热点及技术挑战
AMD正加紧研发Zen4和Zen5
传感器如何助力智慧城市的建设发展
运放输入网络ZI与反馈网络ZF及实例分析
可穿戴智能设备有哪些