Научим кладовщика двигаться при нажатии на кнопки «вверх», «вниз», «влево» и «вправо».
Подключим кнопки к микроконтроллеру. Три кнопки подтянуты к земле, а одна к питанию. Это сделано специально, чтобы обеспечить условия для правильной загрузки микроконтроллера. Микроконтроллер при включении проверяет состояние некоторых выводов и на основании этого принимает решение о режиме работы — выполнение программы или переход в режим перепрошивки. Для нормального режима работы необходимо, чтобы уровень сигнала на выводе 15 был низким, а на 2 и 0 был высоким. Чтобы соблюсти эти условия и пришлось подключить кнопки по-разному.
#include <SPI.h> #include <Adafruit_ST7735.h> #include "LittleFS_ImageReader.h" #define PIN_CS 2 #define PIN_DC 4 #define PIN_RST 5 Adafruit_ST7735 tft = Adafruit_ST7735(PIN_CS, PIN_DC, PIN_RST); LittleFS_ImageReader reader; bool Map[10][8] = { {1,1,0,1,1,1,0,1}, {0,1,1,1,1,1,1,0}, {1,1,0,0,0,1,1,1}, {0,1,0,1,0,1,0,1}, {0,1,0,0,0,1,0,1}, {1,1,1,1,0,0,0,1}, {1,0,0,0,0,0,0,1}, {1,0,0,0,1,0,0,1}, {1,0,0,0,1,1,1,1}, {1,1,1,1,1,0,0,0} }; struct Pos { int x = 0; int y = 0; bool operator == (const Pos &pos) const { return x == pos.x && y == pos.y; } }; class Box { private: Adafruit_ST7735 *tft_ptr; LittleFS_ImageReader *reader_ptr; Pos pos; String picture = "/box.bmp"; String picture_on_gate = "/boxngate.bmp"; bool on_gate = false; public: Box(Adafruit_ST7735 *_tft_ptr, LittleFS_ImageReader *_reader_ptr, int x, int y) { tft_ptr = _tft_ptr; reader_ptr = _reader_ptr; pos.x = x; pos.y = y; } void draw() { if (on_gate) reader_ptr->drawBMP(picture_on_gate, *tft_ptr, pos.x * 16, pos.y * 16); else reader_ptr->drawBMP(picture, *tft_ptr, pos.x * 16, pos.y * 16); } void setOnGate(bool state) { on_gate = state; } bool getOnGate() const { return on_gate; } Pos getPos() const { return pos; } void setPos(Pos _pos) { pos.x = _pos.x; pos.y = _pos.y; draw(); } }; class Gate { private: Adafruit_ST7735 *tft_ptr; LittleFS_ImageReader *reader_ptr; Pos pos; String picture = "/gate.bmp"; public: Gate(Adafruit_ST7735 *_tft_ptr, LittleFS_ImageReader *_reader_ptr, int x, int y) { tft_ptr = _tft_ptr; reader_ptr = _reader_ptr; pos.x = x; pos.y = y; } void draw() const { reader_ptr->drawBMP(picture, *tft_ptr, pos.x * 16, pos.y * 16); } Pos getPos() const { return pos; } }; class Man { private: Adafruit_ST7735 *tft_ptr; LittleFS_ImageReader *reader_ptr; Pos pos; String picture = "/man.bmp"; public: Man(Adafruit_ST7735 *_tft_ptr, LittleFS_ImageReader *_reader_ptr, int x, int y) { tft_ptr = _tft_ptr; reader_ptr = _reader_ptr; pos.x = x; pos.y = y; } void draw() const { reader_ptr->drawBMP(picture, *tft_ptr, pos.x * 16, pos.y * 16); } Pos getPos() const { return pos; } void setPos(Pos _pos) { tft_ptr->fillRect(pos.x * 16, pos.y * 16, 16, 16, ST77XX_BLACK); pos.x = _pos.x; pos.y = _pos.y; draw(); } }; class Button { private: int pin; bool pressState; bool oldState; public: Button(int _pin, bool _pressState) { pin = _pin; setPinMode(); pressState = _pressState; oldState = not _pressState; } bool onPress() { if (state != oldState){ oldState = state; if (state == pressState) return true; } return false; } void setPinMode() const { } }; const int boxes_number = 3; Box boxes[boxes_number] = { {&tft, &reader, 3, 4}, {&tft, &reader, 4, 6}, {&tft, &reader, 2, 7}, }; const int gates_number = 3; Gate gates[gates_number] = { {&tft, &reader, 6, 3}, {&tft, &reader, 6, 4}, {&tft, &reader, 6, 5}, }; Man man(&tft, &reader, 5, 6); Button btn_up(16, HIGH); Button btn_down(15, HIGH); Button btn_left(12, HIGH); Button btn_right(0, LOW); void setup() { LittleFS.begin(); tft.initR(INITR_BLACKTAB); tft.setRotation(2); tft.fillScreen(ST77XX_BLACK); for (int y = 0; y < 10; y++) { for (int x = 0; x < 10; x++) { if (Map[y][x]) reader.drawBMP("/brick.bmp", tft, x * 16, y * 16); } } for (int i = 0; i < boxes_number; i++) { boxes[i].draw(); } for (int i = 0; i < gates_number; i++) { gates[i].draw(); } man.draw(); btn_left.setPinMode(); } void loop() { Pos man_pos = man.getPos(); if (btn_up.onPress()) man.setPos({man_pos.x, man_pos.y - 1}); if (btn_down.onPress()) man.setPos({man_pos.x, man_pos.y + 1}); if (btn_left.onPress()) man.setPos({man_pos.x - 1, man_pos.y}); if (btn_right.onPress()) man.setPos({man_pos.x + 1, man_pos.y}); }
В код прошлого эксперимента мы добавили класс кнопки, который уже использовали ранее, например в проекте секундомера. Объявили 4 кнопки, на выводах 16, 15, 12 и 0:
Button btn_up(16, HIGH); Button btn_down(15, HIGH); Button btn_left(12, HIGH); Button btn_right(0, LOW);
В класс Button мы добавили метод setPinMode()
и вызываем его для кнопки на 12 пине, хотя он уже вызывался в конструкторе. Это необходимо, так как метод дисплея initR()
вызывает переопределение режима пина 12.
btn_left.setPinMode();
В бесконечном цикле постоянно мониторим события нажатия на кнопки:
Pos man_pos = man.getPos(); if (btn_up.onPress()) man.setPos({man_pos.x, man_pos.y - 1}); if (btn_down.onPress()) man.setPos({man_pos.x, man_pos.y + 1}); if (btn_left.onPress()) man.setPos({man_pos.x - 1, man_pos.y}); if (btn_right.onPress()) man.setPos({man_pos.x + 1, man_pos.y});
При нажатии на кнопку «вверх» уменьшаем координату Y и задаем новую координату кладовщику. Аналогичные действия производим при нажатии на другие кнопки. Теперь наш кладовщик может двигаться по экрану. Но сейчас нет никаких ограничений — он свободно проходит сквозь стены и ящики, никак на них не влияя. Теперь самое время реализовать логику игры, описать условия и взаимодействия. Этим займемся в следующем эксперименте.