\r\n\r\n

有機ELディスプレイを用いたArduinoレトロゲーム

自分でレトロゲームを書くのは大変だし、Arduinoのコードも簡単にポンできるのに、と思ったことはありませんか?

自分でレトロゲームを書くのにどれだけの労力がかかるか、ArduinoでPongをコーディングするのがどれだけ簡単か、気になったことはありませんか?Arduinoで動くミニレトロゲーム機を作る方法と、ゼロからポンをコーディングする方法を紹介します。最終的には以下のようになります。

建設計画

これはかなりシンプルな回路です。ポテンショメーター(ポット)でゲームを制御し、有機ELディスプレイはArduinoで駆動します。これはテスト基板で製作しますが、これを常設回路にしてケースに搭載することも考えられます。以前にもポンの再現について書きましたが、今日はゼロからコードを書き、各パーツを分解していく方法を紹介します。

必要なもの

必要なものはこちらです。

  • 1 x Arduino (モデル問わず)
  • 10kポテンショメーター×1
  • 0.96インチI2C有機ELディスプレイ×1台
  • テストプレート1枚
  • 各種オスコネクター > オスコネクターワイヤー

DIYmall 0.96インチ有機ELモジュール 0.96インチI2C IICシリアル128X64有機ELディスプレイモジュール SSD1306ドライバ Arduino 51 MSP420 STIM32 SCR Raspberry PI (1pc X White) BUY NOW ON AMAZON

どのArduinoでも構いませんが、どのモデルを購入したらよいかわからない場合は、購入ガイドをご覧ください。

この有機ELディスプレイはとてもクールです。カラーはホワイト、ブルー、イエロー、3色のミックスから選べます。フルカラーも存在しますが、これはこのプロジェクトに全く別のレベルの複雑さとコストを追加することになります。

サーキット

Arduinoの経験があまりない方は、まずこちらのビギナープロジェクトをご覧ください。

これです。

ポットを見る前に、左のピンを+5Vに、右のピンをグランドに接続し、中央のピンをアナログピン0(A0)に接続してください。

有機ELディスプレイは、I2Cプロトコルで接続されています。VCCとGNDをArduinoの+5VとGNDに接続します。SCLをアナログ5(A5)に接続します。SDAをアナログ4(A4)に接続します。アナログピンと接続する理由は簡単で、このピンにはI2Cプロトコルに必要な回路が搭載されているからです。これらの接続が正しいか、またクロスオーバーがないかを確認してください。正確なピンは機種によって異なりますが、NanoとUnoではA4とA5が使用されています。 ArduinoまたはNanoを使用していない場合は、お使いの機種のWireライブラリのドキュメントをご確認ください。

ポットトライアル

このテストコードをアップロードするには、Tools> board と Tools> port メニューから正しいボードとポートが選択されていることを確認します)。

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(****ogRead(A0)); // print the value from the pot delay(500);}

ここでシリアルモニター(右上>シリアルモニター)をONにして、ポットカバーを回してください。シリアルモニターに値が表示されていることを確認してください。反時計回りに回しきると0、時計回りに回しきると1023になります。

後で調整しますが、今はこれでOKです。何も起こらないか、値が変化しない場合は、回路を切り離して再確認してください。

オールド・テスト

有機ELディスプレイは、構成がやや複雑です。まず、ディスプレイを駆動するためのライブラリを2つインストールする必要があります。GithubからAdafruitのGFXライブラリと、AdafruitのThinksd1306ライブラリをダウンロードします。librariesフォルダにファイルをコピーします。これは、お使いのOSに依存します。

  • Mac OS:/Users/Username/Documents/Arduino/libraries
  • Linux:/home/Username/Sketchbook
  • Windows:/Users/Arduino/library

では、テストスケッチをアップロードしてみましょう。ファイル"> "例"> "Adafruit SSD1306"> "へ移動します。SSD1306 ⇄ 128x64 ⇄ i2c".これにより、多数のグラフィックを含む大きなスケッチが提供されます。

アップロードしても何も起こらない場合は、接続を解除して再確認してください。メニューにexampleがない場合は、Arduino IDEを再起動する必要があるかもしれません。

コード

さて、いよいよコードを書きます。各ステップを説明しますので、動かすだけなら読み飛ばしてください。もし、自信がないのであれば、この10個の無料のリソースを見て、コードを学んでみてください。

1つ目には必要なライブラリが含まれています。

#include <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>

SPIとWIREはI2C通信を扱うArduinoのライブラリで、adafruit_uGFXとAdafruit_SSD1306は以前からインストールされているライブラリです。

次に、モニターの設定を行います。

Adafruit_SSD1306 display(4);

次に、ゲームを実行するために必要なすべての変数を設定します:。

int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};c***t 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;

ゲームの実行に必要なすべてのデータを保存しています。ボールの位置、画面の大きさ、プレイヤーの位置などを記憶するものもあります。なお、これらの中には定数というものがあり、これは一定で変化しないことを意味する。これにより、Arduinoのコンパイラが高速化されます。

画面の解像度とボールの位置が配列で格納されている。配列は似たようなものの集まりで、ボールの場合は座標(XとY)を格納します。配列の要素にアクセスするのは簡単です(このコードはファイルには含めないでください)。

resolution[1];

配列はゼロから始まるので、これは解決配列の2番目の要素(64)を返します。要素の更新はより簡単です(繰り返しますが、このコードは含めないでください)。

ball[1] = 15;

void setup()では、コンフィギュレーションが表示されます。

void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.display();}

最初の行は、ディスプレイの寸法と通信プロトコルをAdafruitライブラリに伝えます(この場合は128 x 64とI2Cです)。2行目(表示。Show () は、バッファに格納されているものを画面に表示するよう指示します(つまり、何も表示しない)。

drablell と eraseBall という名前の2つのメソッドを作成します。

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メソッドを使って、画面上にボールを描画している。これは、先に定義した一定のボールサイズを使用します。これを変更してみて、どうなるか試してみてください。このdrawCircleメソッドには、黒または白の1つのピクセルカラーを受け取ります。モノクロ(1色)表示なので、白は画素がオン、黒は画素がオフになることに相当します。

ここで、moveAiというメソッドを作成します。

void moveAi() { eraseAiPaddle(aiPos); if (ball[1] > aiPos) { ++aiPos; } else if (ball[1] < aiPos) { --aiPos; } drawAiPaddle(aiPos);}

AIやAIプレイヤーを移動させる方法です。ボールがパドルの上にあれば上に移動するという、かなりシンプルなコンピュータ対戦です。パドルの下、下に移動します。シンプルですが、よくできています。インクリメント、デクリメント記号(+aiPos、--aiPos)は、aiPosition に 1 を加算、減算するために使用する。大きな数字を足したり引いたりすることで、AIの動きが速くなり、その結果、倒しにくくなります。その方法は以下の通りです。

aiPos += 2;

とします。

aiPos -= 2;

プラスとマイナスの等号は、aiPosの現在値から2を加算または減算するための省略形です。 この方法にはもう一つ方法があります。

aiPos = aiPos + 2;

aiPos = aiPos - 1;

この方法では、まずスクラッチを消して、それから描画していることに注意してください。これはやらなければならない。新しい位置のパドルが描画されると、画面上に2つのパドルが重なった状態になります。

drawNetメソッドは、2つのループを使ってネットワークを描画する。

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); }}

これは、壁の幅の変数を使用して、そのサイズを設定します。

drawPixels と erasePixels というメソッドを作成します。ボール方式と同様、この2つの方式の違いは、画素の色だけです。

void drawPixel(int posX, int posY, int dimensi***) { for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), WHITE); } }}void erasePixel(int posX, int posY, int dimensi***) { for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), BLACK); } }}

繰り返しになるが、どちらの方法も2つのforループを使って、画素のセットを描画する。ライブラリの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があり、ピクセルを黒またはオフに設定します。

最後の4つの方法は非常によく似ています。これらは、プレーヤーと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 <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>Adafruit_SSD1306 display(4);int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};c***t 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 = ****ogRead(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 dimensi***) { // draw group of pixels for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), WHITE); } }}void erasePixel(int posX, int posY, int dimensi***) { // erase group of pixels for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++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、ボールスピードの変更)。
  • ボールやAIにランダムな動きを追加してください。
  • 2人分にはもう1瓶追加します。
  • 一時停止ボタンを追加する。

それでは、レトロゲーム「Pi zero」のアイテムをご覧ください。

このパスワードでピンポン玉をコーディングしたのか、どんな変更を加えたのか、下のコメントで教えてください。

  • 2021-03-16 17:37 に公開
  • 閲覧 ( 25 )
  • 分類:IT

あなたが興味を持っているかもしれない記事

匿名者
匿名者

0 件の投稿

作家リスト

  1. admin 0 投稿
  2. 匿名者 0 投稿

おすすめ