Buat game mobil yang cepat, sederhana, dan seru memakai Arduino, satu tombol, dan layar LCD I2C 16x2. Mobilmu harus melompat melewati gedung-gedung yang datang dari kanan dengan menekan satu tombol. Proyek ini cocok untuk belajar memakai LCD I2C dengan kabel yang lebih sedikit, membuat karakter custom, dan memodifikasi game yang sudah ada.
Bahan & Komponen
Daftar perangkat keras, modul, dan tools yang dibutuhkan untuk merakit proyek ini.
Daftar Komponen & Perangkat Keras
1x Arduino UNO (Mikrokontroler pemroses utama; menurut pembuat, kamu juga bisa memakai Arduino NANO)
1x Modul LCD 16x2 I2C (Layar game; memakai I2C sehingga kabelnya lebih sedikit dan tidak perlu dipasang di breadboard)
1x Tactile Switch (tombol push biasa) (Tombol untuk membuat mobil melompat)
2x Kabel Jumper (generik) (Koneksi untuk tombol)
4x Kabel Jumper Male/Female (Koneksi untuk layar LCD)
1x Kabel USB-A ke USB-B (Untuk catu daya dan meng-upload program)
Software & Library
Arduino IDE (Untuk menulis dan meng-upload program)
Library LiquidCrystal_I2C (Mengontrol LCD lewat I2C; library Wire sudah bawaan Arduino IDE)
Alat
1x Breadboard 170 pin (Breadboard sederhana untuk memasang tombol)
Langkah Pembuatan
Khusus Member
Panduan perakitan tahap demi tahap dan catatan proses pengerjaan.
Halo, calon game developer! Proyek ini adalah game mobil yang cepat, sederhana, dan seru, memakai layar LCD I2C, dan dibuat berdasarkan game lompat (jumping game) yang sudah ada.
Game mobil dengan Arduino dan layar LCD I2C 16x2
Langkah 1: Latar Belakang dan Ide
Pembuat kadang ingin membuat game, tetapi belum tahu caranya. Ia lalu mencari game yang memakai LCD dan tombol di Project Hub, dan menemukan game lompat dengan satu tombol. Dari situ ia memodifikasinya menjadi game mobil.
Idenya sederhana: membuat game mobil yang cepat memakai LCD I2C, bukan LCD biasa, karena kabelnya lebih sedikit dan tidak perlu dipasang di breadboard. Tombol akan mengaktifkan mobil sehingga mobil bergerak naik (melompat).
Langkah 2: Siapkan Komponen
Kumpulkan Arduino UNO (atau NANO), LCD 16x2 I2C, satu tombol, kabel jumper, dan breadboard kecil untuk memasang tombol. Lihat daftar lengkap pada bagian Bahan & Komponen.
Langkah 3: Rakit Rangkaian
Hubungkan tombol dan layar LCD I2C ke Arduino sesuai diagram pada bagian Skema Rangkaian. Menurut pembuat, 4 kabel jumper male/female dipakai untuk layar dan 2 kabel jumper untuk tombol.
Skema rangkaian game mobil (sudah diedit)
Langkah 4: Buat Karakter Custom
Pembuat mengubah balok sederhana dan karakter pahlawan dari game aslinya menjadi gedung dan mobil. Kamu bisa membuat karakter custom sendiri dengan generator karakter LCD di https://omerk.github.io/lcdchargen/, yang mudah dipakai. Di kode, bentuk karakter ditulis sebagai deretan angka biner 5x8 piksel dan didaftarkan ke LCD memakai lcd.createChar().
Pada halaman asli ada gambar perbandingan sebelum dan sesudah modifikasi ("Before modifications" dari game Arduino Game By LCD, dan "After modifications" buatan pembuat). Gambar tersebut tidak muncul dalam hasil ekstraksi, jadi tidak dapat disertakan di sini.
Langkah 5: Upload Kode Program
Pasang library LiquidCrystal_I2C di Arduino IDE (Sketch > Include Library > Manage Libraries), salin kode lengkap pada bagian Kode Program, lalu upload ke Arduino. Alamat I2C LCD pada kode adalah 0x27. Jika layar tidak menampilkan apa-apa, alamat modulmu mungkin berbeda.
Beberapa hal yang bisa kamu amati di kode:
Tombol dihubungkan ke pin 2 dan dibaca dengan interrupt (attachInterrupt, mode FALLING), sehingga tombol tetap terbaca meski program sedang menggambar layar.
Rintangan (gedung) bergeser dari kanan ke kiri dan muncul secara acak lewat random(), dengan panjang dan jarak yang berbeda-beda.
Skor bertambah seiring jarak yang ditempuh dan ditampilkan di pojok kanan atas layar.
Jika mobil menabrak sesuatu, game berhenti dan layar menampilkan tulisan "Press Start" yang berkedip. Tekan tombol untuk bermain lagi.
Ada pin PIN_AUTOPLAY (pin 1) yang diatur sebagai output pada kode asli game lompat ini.
Langkah 6: Mainkan!
Setelah kode ter-upload, tekan tombol untuk memulai, lalu tekan lagi setiap kali mobil perlu melompati gedung. Video demo proyek tersedia di halaman asli di Hackster.io ("The project working"). Tantangan untukmu: ubah bentuk mobil dan gedung, atau ubah jarak antar rintangan untuk membuat game lebih mudah atau lebih sulit.
🔒
Akses Gratis Khusus Member
Buka Seluruh Panduan, Skema & Source Code
Dapatkan akses penuh ke langkah perakitan detail, diagram sirkuit lengkap, dan source code siap pakai. Gratis selamanya!
Halo, calon game developer! Proyek ini adalah game mobil yang cepat, sederhana, dan seru, memakai layar LCD I2C, dan dibuat berdasarkan game lompat (jumping game) yang sudah ada.
Game mobil dengan Arduino dan layar LCD I2C 16x2
Langkah 1: Latar Belakang dan Ide
Pembuat kadang ingin membuat game, tetapi belum tahu caranya. Ia lalu mencari game yang memakai LCD dan tombol di Project Hub, dan menemukan game lompat dengan satu tombol. Dari situ ia memodifikasinya menjadi game mobil.
Idenya sederhana: membuat game mobil yang cepat memakai LCD I2C, bukan LCD biasa, karena kabelnya lebih sedikit dan tidak perlu dipasang di breadboard. Tombol akan mengaktifkan mobil sehingga mobil bergerak naik (melompat).
Langkah 2: Siapkan Komponen
Kumpulkan Arduino UNO (atau NANO), LCD 16x2 I2C, satu tombol, kabel jumper, dan breadboard kecil untuk memasang tombol. Lihat daftar lengkap pada bagian Bahan & Komponen.
Langkah 3: Rakit Rangkaian
Hubungkan tombol dan layar LCD I2C ke Arduino sesuai diagram pada bagian Skema Rangkaian. Menurut pembuat, 4 kabel jumper male/female dipakai untuk layar dan 2 kabel jumper untuk tombol.
Skema rangkaian game mobil (sudah diedit)
Langkah 4: Buat Karakter Custom
Pembuat mengubah balok sederhana dan karakter pahlawan dari game aslinya menjadi gedung dan mobil. Kamu bisa membuat karakter custom sendiri dengan generator karakter LCD di https://omerk.github.io/lcdchargen/, yang mudah dipakai. Di kode, bentuk karakter ditulis sebagai deretan angka biner 5x8 piksel dan didaftarkan ke LCD memakai lcd.createChar().
Pada halaman asli ada gambar perbandingan sebelum dan sesudah modifikasi ("Before modifications" dari game Arduino Game By LCD, dan "After modifications" buatan pembuat). Gambar tersebut tidak muncul dalam hasil ekstraksi, jadi tidak dapat disertakan di sini.
Langkah 5: Upload Kode Program
Pasang library LiquidCrystal_I2C di Arduino IDE (Sketch > Include Library > Manage Libraries), salin kode lengkap pada bagian Kode Program, lalu upload ke Arduino. Alamat I2C LCD pada kode adalah 0x27. Jika layar tidak menampilkan apa-apa, alamat modulmu mungkin berbeda.
Beberapa hal yang bisa kamu amati di kode:
Tombol dihubungkan ke pin 2 dan dibaca dengan interrupt (attachInterrupt, mode FALLING), sehingga tombol tetap terbaca meski program sedang menggambar layar.
Rintangan (gedung) bergeser dari kanan ke kiri dan muncul secara acak lewat random(), dengan panjang dan jarak yang berbeda-beda.
Skor bertambah seiring jarak yang ditempuh dan ditampilkan di pojok kanan atas layar.
Jika mobil menabrak sesuatu, game berhenti dan layar menampilkan tulisan "Press Start" yang berkedip. Tekan tombol untuk bermain lagi.
Ada pin PIN_AUTOPLAY (pin 1) yang diatur sebagai output pada kode asli game lompat ini.
Langkah 6: Mainkan!
Setelah kode ter-upload, tekan tombol untuk memulai, lalu tekan lagi setiap kali mobil perlu melompati gedung. Video demo proyek tersedia di halaman asli di Hackster.io ("The project working"). Tantangan untukmu: ubah bentuk mobil dan gedung, atau ubah jarak antar rintangan untuk membuat game lebih mudah atau lebih sulit.
Skema & Diagram Sirkuit
Rangkaian ini sangat sederhana dan hanya terdiri dari dua bagian yang terhubung ke Arduino UNO:
Tombol (tactile switch): dihubungkan ke pin digital 2 Arduino dan GND. Tombol dipasang di breadboard kecil dengan 2 kabel jumper. Pada kode, pin 2 diatur sebagai input dengan pull-up internal (digitalWrite(PIN_BUTTON, HIGH)), sehingga tombol terbaca saat pin turun ke LOW (mode FALLING).
LCD 16x2 I2C: memakai antarmuka I2C dengan 4 kabel jumper male/female: VCC, GND, SDA, dan SCL (pada Arduino UNO standar, SDA ke A4 dan SCL ke A5).
Catu daya diambil dari Arduino lewat kabel USB-A ke USB-B.
Diagram Rangkaian (Schematics - Edited)
Kode Program
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#define PIN_BUTTON 2
#define PIN_AUTOPLAY 1
#define SPRITE_RUN1 1 //Car sprite
#define SPRITE_RUN2 2
#define SPRITE_JUMP 3
#define SPRITE_JUMP_UPPER '.'
#define SPRITE_JUMP_LOWER 4
#define SPRITE_TERRAIN_EMPTY ' '
#define SPRITE_TERRAIN_SOLID 5
#define SPRITE_TERRAIN_SOLID_RIGHT 6
#define SPRITE_TERRAIN_SOLID_LEFT 7
#define CAR_HORIZONTAL_POSITION 1 // Horizontal position of CAR on screen
#define TERRAIN_WIDTH 16
#define TERRAIN_EMPTY 0
#define TERRAIN_LOWER_BLOCK 1
#define TERRAIN_UPPER_BLOCK 2
#define CAR_POSITION_OFF 0 // CAR is invisible
#define CAR_POSITION_RUN_LOWER_1 1 // CAR is running on lower row (pose 1)
#define CAR_POSITION_RUN_LOWER_2 2 // (pose 2)
#define CAR_POSITION_JUMP_1 3 // Starting a jump
#define CAR_POSITION_JUMP_2 4 // Half-way up
#define CAR_POSITION_JUMP_3 5 // Jump is on upper row
#define CAR_POSITION_JUMP_4 6 // Jump is on upper row
#define CAR_POSITION_JUMP_5 7 // Jump is on upper row
#define CAR_POSITION_JUMP_6 8 // Jump is on upper row
#define CAR_POSITION_JUMP_7 9 // Half-way down
#define CAR_POSITION_JUMP_8 10 // About to land
#define CAR_POSITION_RUN_UPPER_1 11 // CAR is running on upper row (pose 1)
#define CAR_POSITION_RUN_UPPER_2 12 // (pose 2)
LiquidCrystal_I2C lcd(0x27, 16, 2);
static char terrainUpper[TERRAIN_WIDTH + 1];
static char terrainLower[TERRAIN_WIDTH + 1];
static bool buttonPushed = false;
void initializeGraphics() {
static byte graphics[] = {
// Run position 1
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Run position 2
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Jump
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Jump lower
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Ground
0b01110,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
// Ground right
0b01110,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
// Ground left
0b01110,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
};
int i;
// Skip using character 0, this allows lcd.print() to be used to
// quickly draw multiple characters
for (i = 0; i < 7; ++i) {
lcd.createChar(i + 1, &graphics[i * 8]);
}
for (i = 0; i < TERRAIN_WIDTH; ++i) {
terrainUpper[i] = SPRITE_TERRAIN_EMPTY;
terrainLower[i] = SPRITE_TERRAIN_EMPTY;
}
}
// Slide the terrain to the left in half-character increments
//
void advanceTerrain(char* terrain, byte newTerrain) {
for (int i = 0; i < TERRAIN_WIDTH; ++i) {
char current = terrain[i];
char next = (i == TERRAIN_WIDTH - 1) ? newTerrain : terrain[i + 1];
switch (current) {
case SPRITE_TERRAIN_EMPTY:
terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY;
break;
case SPRITE_TERRAIN_SOLID:
terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID;
break;
case SPRITE_TERRAIN_SOLID_RIGHT:
terrain[i] = SPRITE_TERRAIN_SOLID;
break;
case SPRITE_TERRAIN_SOLID_LEFT:
terrain[i] = SPRITE_TERRAIN_EMPTY;
break;
}
}
}
bool drawCAR(byte position, char* terrainUpper, char* terrainLower, unsigned int score) {
bool collide = false;
char upperSave = terrainUpper[CAR_HORIZONTAL_POSITION];
char lowerSave = terrainLower[CAR_HORIZONTAL_POSITION];
byte upper, lower;
switch (position) {
case CAR_POSITION_OFF:
upper = lower = SPRITE_TERRAIN_EMPTY;
break;
case CAR_POSITION_RUN_LOWER_1:
upper = SPRITE_TERRAIN_EMPTY;
lower = SPRITE_RUN1;
break;
case CAR_POSITION_RUN_LOWER_2:
upper = SPRITE_TERRAIN_EMPTY;
lower = SPRITE_RUN2;
break;
case CAR_POSITION_JUMP_1:
case CAR_POSITION_JUMP_8:
upper = SPRITE_TERRAIN_EMPTY;
lower = SPRITE_JUMP;
break;
case CAR_POSITION_JUMP_2:
case CAR_POSITION_JUMP_7:
upper = SPRITE_JUMP_UPPER;
lower = SPRITE_JUMP_LOWER;
break;
case CAR_POSITION_JUMP_3:
case CAR_POSITION_JUMP_4:
case CAR_POSITION_JUMP_5:
case CAR_POSITION_JUMP_6:
upper = SPRITE_JUMP;
lower = SPRITE_TERRAIN_EMPTY;
break;
case CAR_POSITION_RUN_UPPER_1:
upper = SPRITE_RUN1;
lower = SPRITE_TERRAIN_EMPTY;
break;
case CAR_POSITION_RUN_UPPER_2:
upper = SPRITE_RUN2;
lower = SPRITE_TERRAIN_EMPTY;
break;
}
if (upper != ' ') {
terrainUpper[CAR_HORIZONTAL_POSITION] = upper;
collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true;
}
if (lower != ' ') {
terrainLower[CAR_HORIZONTAL_POSITION] = lower;
collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true;
}
byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;
// Draw the scene
terrainUpper[TERRAIN_WIDTH] = '\0';
terrainLower[TERRAIN_WIDTH] = '\0';
char temp = terrainUpper[16 - digits];
terrainUpper[16 - digits] = '\0';
lcd.setCursor(0, 0);
lcd.print(terrainUpper);
terrainUpper[16 - digits] = temp;
lcd.setCursor(0, 1);
lcd.print(terrainLower);
lcd.setCursor(16 - digits, 0);
lcd.print(score);
terrainUpper[CAR_HORIZONTAL_POSITION] = upperSave;
terrainLower[CAR_HORIZONTAL_POSITION] = lowerSave;
return collide;
}
// Handle the button push as an interrupt
void buttonPush() {
buttonPushed = true;
}
void setup() {
pinMode(PIN_BUTTON, INPUT);
digitalWrite(PIN_BUTTON, HIGH);
pinMode(PIN_AUTOPLAY, OUTPUT);
digitalWrite(PIN_AUTOPLAY, HIGH);
// Digital pin 2 maps to interrupt 0
attachInterrupt(0/*PIN_BUTTON*/, buttonPush, FALLING);
initializeGraphics();
lcd.init();
lcd.backlight();
}
void loop() {
static byte CARPos = CAR_POSITION_RUN_LOWER_1;
static byte newTerrainType = TERRAIN_EMPTY;
static byte newTerrainDuration = 1;
static bool playing = false;
static bool blink = false;
static unsigned int distance = 0;
if (!playing) {
drawCAR((blink) ? CAR_POSITION_OFF : CARPos, terrainUpper, terrainLower, distance >> 3);
if (blink) {
lcd.setCursor(0, 0);
lcd.print("Press Start");
}
delay(100);
blink = !blink;
if (buttonPushed) {
initializeGraphics();
CARPos = CAR_POSITION_RUN_LOWER_1;
playing = true;
buttonPushed = false;
distance = 0;
}
return;
}
// Shift the terrain to the left
advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
// Make new terrain to enter on the right
if (--newTerrainDuration == 0) {
if (newTerrainType == TERRAIN_EMPTY) {
newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK;
newTerrainDuration = 10 + random(6);
} else {
newTerrainType = TERRAIN_EMPTY;
newTerrainDuration = 10 + random(6);
}
}
if (buttonPushed) {
if (CARPos <= CAR_POSITION_RUN_LOWER_2) CARPos = CAR_POSITION_JUMP_1;
buttonPushed = false;
}
if (drawCAR(CARPos, terrainUpper, terrainLower, distance >> 3)) {
playing = false; // The CAR collided with something. Too bad.
for (int i = 0; i <= 2; i++) {
}
} else {
if (CARPos == CAR_POSITION_RUN_LOWER_2 || CARPos == CAR_POSITION_JUMP_8) {
CARPos = CAR_POSITION_RUN_LOWER_1;
} else if ((CARPos >= CAR_POSITION_JUMP_3 && CARPos <= CAR_POSITION_JUMP_5) && terrainLower[CAR_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) {
CARPos = CAR_POSITION_RUN_UPPER_1;
} else if (CARPos >= CAR_POSITION_RUN_UPPER_1 && terrainLower[CAR_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) {
CARPos = CAR_POSITION_JUMP_5;
} else if (CARPos == CAR_POSITION_RUN_UPPER_2) {
CARPos = CAR_POSITION_RUN_UPPER_1;
} else {
++CARPos;
}
++distance;
digitalWrite(PIN_AUTOPLAY, terrainLower[CAR_HORIZONTAL_POSITION + 2] == SPRITE_TERRAIN_EMPTY ? HIGH : LOW);
}
}