In diesem Projekt wird eine sehr rudimentäre Spielekonsole entworfen, gebaut und mit einem Spiel programmiert. Die Herausforderung an diesem Setup ist, dass das LCD nicht allzu viele Möglichkeiten der Darstellung hat, d.h. das gesamte Spielfeld beschränkt sich auf 16×2 Zeichen. Außerdem können ca. 200 Zeichen dargestellt werden, die auf dem internen CGROM des LCD-Moduls fest eingespeichert sind. Darüber hinaus sind noch 8 selbst definierbare Zeichen möglich, hier muss man sich also auf eigene Sprites sehr beschränken.
Die Konsole benötigt eine Anzeige für das Spiel (hier: ein 16x2 LCD-Panel), einige Bedienelemente (hier:
vier Mikrotaster) und einige zusätzliche Indikatoranzeigen, die auf dem LCD keinen Platz mehr haben
(hier: fünf verschiedenfarbige LEDs).
Alles zusammen wird von einem Arduino (Uno/Nano) gesteuert, der auch für die Spiel-Logik verantwortlich ist.
Der Prototyp wird auf dem Breadboard bzw. mehreren Breadboards aufgebaut. Für mehr Details über das LCD-Panel kann man mehr auf der Seite "LCD-Anzeige" erfahren und über die genaue Funktionsweise der Taster-Anschlüssen auf "Viele Schalter - 1 Pin". Mehr Details zum Piezo-Element gibt es auf "Sound mit Piezo".
Der Spieler steuert seine Figur - den kleinen Ninja 忍者. Dieser läuft durch die dunklen Gänge der Burg
Osaka in Japan. Sein Ziel ist es, bis zum bösen und grausamen Herrscher Akuyaku 悪役 vorzudringen und
einen Attentat auf ihn zu verüben. Nur der Ninja besitzt die Geschicklichkeit, die Wachen und Fallen der
Burg zu überwinden.
Der Ninja läuft ununterbrochen durch die Gänge und muss über Hindernisse oder Feinde springen
(Taste A
). Er selbst kann Pfeile Shuriken werfen (Taste D
), um damit Feinde zu
erledigen.
/**
* How to play:
* You little ninja is running through the corridors of Osaka castle to avoid
* obstacles and kill enemies (dogs and guards). Try to run as far a possible!
*/
#include <LiquidCrystal.h>
#define PIN_LCD_D4 2
#define PIN_LCD_D5 3
#define PIN_LCD_D6 4
#define PIN_LCD_D7 5
#define PIN_PIEZO 6
#define PIN_LED1 7
#define PIN_LED2 8
#define PIN_LED3 9
#define PIN_LED4 10
#define PIN_LCD_EN 11
#define PIN_LCD_RS 12
#define PIN_LED5 13
#define PIN_BUTTONS A0
#define LCD_WIDTH 16
#define LCD_HEIGHT 2
#define GAME_PHASE_INIT 0
#define GAME_PHASE_END 1
#define GAME_PHASE_RUNNING 2
#define BUTTON_A 0
#define BUTTON_B 1
#define BUTTON_C 2
#define BUTTON_D 3
#define LED_PINK 0
#define LED_BLUE 1
#define LED_GREEN 2
#define LED_YELLOW 3
#define LED_RED 4
#define NINJA_1 0
#define NINJA_2 1
#define DOG 2
#define ENEMY 3
#define OBSTACLE 4
LiquidCrystal lcd(PIN_LCD_RS, PIN_LCD_EN, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7);
bool buttonPressed[4] = {false, false, false, false}, isSound;
byte ledStatus[5] = {0, 0, 0, 0, 0};
byte ledPins[5] = {PIN_LED1, PIN_LED2, PIN_LED3, PIN_LED4, PIN_LED5};
byte gamePhase, ninjaFrame, ninjaPos, shootPos, levelData[LCD_WIDTH];
byte graphics[][8] = {
{B01100, B01100, B00000, B01110, B11100, B01100, B11010, B10011}, // Ninja 1
{B01100, B01100, B00000, B01100, B01100, B01100, B01100, B01110}, // Ninja 2
{B00000, B00000, B00000, B01000, B11111, B00111, B00101, B00101}, // Dog
{B01110, B01101, B00000, B10110, B11110, B00111, B01001, B11011}, // Enemy
{B00000, B00000, B00000, B11111, B10111, B11011, B11101, B11111}, // Obstacle
};
int gameScore, gameSpeed;
unsigned long lastAction, lastJump, lastRelease;
void setup()
{
lcd.begin(LCD_WIDTH, LCD_HEIGHT);
pinMode(PIN_LED1, OUTPUT);
pinMode(PIN_LED2, OUTPUT);
pinMode(PIN_LED3, OUTPUT);
pinMode(PIN_LED4, OUTPUT);
pinMode(PIN_LED5, OUTPUT);
pinMode(PIN_PIEZO, OUTPUT);
pinMode(PIN_BUTTONS, INPUT);
digitalWrite(PIN_LED1, LOW);
digitalWrite(PIN_LED2, LOW);
digitalWrite(PIN_LED3, LOW);
digitalWrite(PIN_LED4, LOW);
digitalWrite(PIN_LED5, LOW);
lcd.createChar(NINJA_1, graphics[NINJA_1]);
lcd.createChar(NINJA_2, graphics[NINJA_2]);
lcd.createChar(DOG, graphics[DOG]);
lcd.createChar(ENEMY, graphics[ENEMY]);
lcd.createChar(OBSTACLE, graphics[OBSTACLE]);
randomSeed(analogRead(A5));
gamePhase = GAME_PHASE_INIT;
}
void loop()
{
checkButtons();
switch (gamePhase) {
case GAME_PHASE_INIT:
lcd.clear();
lcd.print("Ritoruninja");
tone(PIN_PIEZO, 440, 220); delay(220);
tone(PIN_PIEZO, 440, 200); delay(220);
tone(PIN_PIEZO, 660, 220); delay(220);
tone(PIN_PIEZO, 440, 220); delay(220);
tone(PIN_PIEZO, 660, 750); delay(750);
lcd.setCursor(0, 1);
lcd.print("- press A -");
while (!buttonPressed[BUTTON_A]) {
checkButtons();
}
lcd.clear();
initGame();
break;
case GAME_PHASE_END:
lcd.clear();
lcd.print("Game over!");
for (byte i = 0; i < 12; i++) {
tone(PIN_PIEZO, 200 - i * 10, 100);
delay(100);
}
lcd.setCursor(0, 1);
lcd.print("Score: ");
lcd.print(gameScore);
delay(1500);
while (!buttonPressed[BUTTON_A]) {
checkButtons();
}
gamePhase = GAME_PHASE_INIT;
break;
case GAME_PHASE_RUNNING:
jumpNinja();
shootNinja();
if ((millis() - lastAction) > gameSpeed) {
updateLevel();
updateNinja();
lastAction = millis();
}
break;
}
}
void initGame()
{
gamePhase = GAME_PHASE_RUNNING;
gameScore = 0;
gameSpeed = 450;
isSound = false;
ninjaFrame = 0;
ninjaPos = 1;
shootPos = 0;
lastAction = millis();
lastJump = lastAction;
for (byte i = 0; i < LCD_WIDTH; i++) {
levelData[i] = 0;
}
}
void checkButtons()
{
int pinValue = analogRead(PIN_BUTTONS);
for (byte i = 0; i < 4; i++) {
buttonPressed[i] = false;
}
if (pinValue < 520 && pinValue > 500) { // 510
buttonPressed[BUTTON_A] = true;
} else if (pinValue < 350 && pinValue > 330) { //340
buttonPressed[BUTTON_B] = true;
} else if (pinValue < 265 && pinValue > 245) { // 255
buttonPressed[BUTTON_C] = true;
} else if (pinValue < 210 && pinValue > 190) { // 203
buttonPressed[BUTTON_D] = true;
}
}
bool toggleLed(byte ledNumber)
{
ledStatus[ledNumber]++;
ledStatus[ledNumber] %= 2;
digitalWrite(ledPins[ledNumber], ledStatus[ledNumber]);
if (ledStatus[ledNumber] == 1) {
return true;
}
return false;
}
void jumpNinja()
{
if ((millis() - lastJump) > 800) {
if ((millis() - lastRelease > 500) && ninjaPos == 1 && buttonPressed[BUTTON_A] == true) {
ninjaPos = 0;
lastJump = millis();
} else if (ninjaPos == 0) {
ninjaPos = 1;
lastRelease = millis();
}
}
}
void shootNinja()
{
if (shootPos == 0 && ninjaPos == 1 && buttonPressed[BUTTON_B] == true) {
shootPos = 3;
}
}
void updateNinja()
{
if (ninjaPos == 0) {
if (levelData[2] == 0) {
lcd.setCursor(2, 1);
lcd.print(" ");
}
lcd.setCursor(2, ninjaPos);
lcd.write((byte)0);
} else {
lcd.setCursor(2, 0);
lcd.print(" ");
lcd.setCursor(2, ninjaPos);
lcd.write(ninjaFrame++);
ninjaFrame %= 2;
}
}
void updateLevel()
{
if (shootPos > 0) {
shootPos++;
if (shootPos >= LCD_WIDTH) {
Serial.println("shoot ends");
shootPos = 0;
}
if (levelData[shootPos] == OBSTACLE || levelData[shootPos - 1] == OBSTACLE) {
Serial.println("hit obstacle");
shootPos = 0;
}
if (levelData[shootPos] == DOG) {
Serial.println("killed dog");
levelData[shootPos] = 0;
shootPos = 0;
gameScore += 2;
}
if (levelData[shootPos - 1] == DOG) {
Serial.println("killed dog");
levelData[shootPos - 1] = 0;
shootPos = 0;
gameScore += 2;
}
if (levelData[shootPos] == ENEMY) {
Serial.println("killed enemy");
levelData[shootPos] = 0;
shootPos = 0;
gameScore += 4;
}
if (levelData[shootPos - 1] == ENEMY) {
Serial.println("killed ENEMY");
levelData[shootPos - 1] = 0;
shootPos = 0;
gameScore += 4;
}
}
for (byte i = 1; i < LCD_WIDTH; i++) {
levelData[i - 1] = levelData[i];
}
// check ninja hit
if (levelData[2] != 0) {
if (ninjaPos == 1) {
gamePhase = GAME_PHASE_END;
} else {
gameScore++;
}
}
// generate new obstacle/enemy
byte rnd = random(100);
if (rnd < 5 && levelData[LCD_WIDTH - 2] == 0) {
levelData[LCD_WIDTH - 1] = OBSTACLE;
} else if (rnd < 7 && levelData[LCD_WIDTH - 2] == 0) {
levelData[LCD_WIDTH - 1] = DOG;
} else if (rnd < 9 && levelData[LCD_WIDTH - 2] == 0) {
levelData[LCD_WIDTH - 1] = ENEMY;
} else {
levelData[LCD_WIDTH - 1] = 0;
}
lcd.setCursor(0, 1);
for (byte i = 0; i < LCD_WIDTH; i++) {
if (levelData[i] != 0) {
lcd.write(levelData[i]);
} else {
lcd.print(" ");
}
}
if (shootPos > 0) {
lcd.setCursor(shootPos, 1);
lcd.print((char)126);
}
if (gameSpeed > 80 && rnd < 50) {
gameSpeed -= 3;
}
}