🤖Have you ever tried Chat.M5Stack.com before asking??😎
    M5Stack Community
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Register
    • Login

    Урок 7. MPU9250. Игра "SPACE DEFENSE" (бета-версия)

    Русскоязычный форум
    1
    1
    3.7k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • DimiD
      Dimi
      last edited by

      Цель урока

      Привет! Сегодня мы научимся работать со встроенным датчиком MPU9250 в серебристой модели M5STACK.
      Датчик MPU9250 включает в себя гироскоп, акселерометр и компас. В данном уроке мы напишем игру "SPACE DEFENSE" (рис. 1), управлять кораблём будет возможно только при помощи наклона устройства. По оси X - движение корабля, а по оси Y - открытие огня. Гироскоп и компас оставим на самостоятельное изучение.

      Рисунок 1. Экран начала игры

      Краткая теория

      Рисунок 2. Устройство механического акселерометра

      Что же такое акселерометр? Акселерометр – прибор (рис. 2), измеряющий проекцию кажущегося ускорения (разности между истинным ускорением объекта и гравитационным ускорением). Как правило, акселерометр представляет собой чувствительную массу, закреплённую в упругом подвесе. Отклонение массы от её первоначального положения при наличии кажущегося ускорения несёт информацию о величине этого ускорения.

      Более подробная информация на Wiki: https://en.wikipedia.org/wiki/Accelerometer

      Перечень компонентов для урока

      • M5STACK серебристая модель со встроенным MPU9250.

      Начнём!

      Шаг 1. Нарисуем картинки

      Нарисуем космический корабль, взрыв корабля и логотип игры. Используйте любой удобный для Вас графический редактор. Мы будем использовать как всегда Paint (рис. 3 – 3.2).

      Рисунок 3. Рисуем космический корабль

      Рисунок 3.1. Рисуем логотип игры

      Рисунок 3.2. Коллекция рисунков для проекта

      В дальнейшем подключим изображения к нашему новому проекту:

      extern unsigned char craft[];
      extern unsigned char craft_logo[];
      extern unsigned char explode[];
      extern unsigned char logo[];
      

      Шаг 2. Пули и космический мусор

      Рассмотрим на примере пуль. Сделаем структуру bulletsObject, которая включает в себя координаты размеры и состояние пули (если пуля в полёте, то busy = true).

      struct bulletsObject
      {
      	int x;
      	int y;
      	int width = 3;
      	int height = 6;
      	bool busy;
      };
      

      Сделаем буфер, содержащий информацию о трёх пулях:

      const int bullets_max = 3;
      bulletsObject bulletsObjectArray[bullets_max];
      

      Космический мусор (если hidden = true, то объект нейтрализован):

      struct spaceDebrisObject
      {
        int x;
        int y;
        int width = 15;
        int height = 15;
        bool hidden;
      };
      

      Сделаем буфер, содержащий информацию о космическом мусоре:

      const int spaceDebris_max = 100;
      spaceDebrisObject spaceDebrisArray[spaceDebris_max];
      

      Шаг 3. Двигаем то, чего много

      Многозадачность? Нет – всё проще. Помните в одной из статей на сайте Arduino было рассказано, как реализовать мигание светодиодом без функции delay, которая заставляет микроконтроллер бездействовать? Если нет, то айда почитать https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay.
      Аналогичным образом поступим и мы (рис. 5). Когда придёт время, тогда выполним необходимые действия и обновим значение предыдущего времени millis.

      Рисунок 5.

      Функция fire вызывается из бесконечного цикла gameLoop() и принимает в качестве аргумента целочисленное значение вектора наклона по оси Y. Если значение вектора менее, чем -250, то будет открыт огонь. О том, как получать значения векторов наклона поговорим в следующем шаге.
      Движение происходит на экране по оси Y всего массива с графическими объектами (рис. 5.1).

      Рисунок 5.1. Принцип движения графических объектов

      void gameLoop() {
        while (true)
        {
          if (!moveCraft(getAccel('X'))) break;
          if (!moveSpaceDebris()) break;
          fire(getAccel('Y'));
          if (health < 0) break;
        }
        craftExplode();  
      }
      
      void fire(int vector) {
        if (vector < -250)
        {
          if (!openFire)
          {
            for (int i = 0; i < bullets_max; i++)
            {
              if (!bulletsObjectArray[i].busy)
              {
                openFire = true;
                bulletsObjectArray[i].x = ((craft_x + (craft_width / 2)) - 1);
                bulletsObjectArray[i].y = craft_y;
                bulletsObjectArray[i].busy = true;
                break;
              }
            }
          }
        }
        else
        {
          openFire = false;
        }
      
        unsigned long millis_ = millis();
        if (((millis_ - moveBullets_pre_millis) >= moveBullets_time))
        {
          moveBullets_pre_millis = millis_; 
          for (int i = 0; i < bullets_max; i++)
          {
            M5.Lcd.fillRect(bulletsObjectArray[i].x, bulletsObjectArray[i].y, bulletsObjectArray[i].width, bulletsObjectArray[i].height, 0x0000);
            if (bulletsObjectArray[i].busy)
            {
              bulletsObjectArray[i].y--;
              M5.Lcd.fillRect(bulletsObjectArray[i].x, bulletsObjectArray[i].y, bulletsObjectArray[i].width, bulletsObjectArray[i].height, 0xff80);
              if (bulletsObjectArray[i].y <= statusBar_height)
              {
                bulletsObjectArray[i].busy = false;
              }
              for (int j = 0; j < spaceDebris_max; j++)
              {
                if (((((spaceDebrisArray[j].x + spaceDebrisArray[j].width) >= bulletsObjectArray[i].x) && (spaceDebrisArray[j].x <= (bulletsObjectArray[i].x + bulletsObjectArray[i].width))) && ((spaceDebrisArray[j].y + spaceDebrisArray[j].height) >= (bulletsObjectArray[i].y + (bulletsObjectArray[i].height / 2))) && (spaceDebrisArray[j].y <= (bulletsObjectArray[i].y + bulletsObjectArray[i].height)) && (!spaceDebrisArray[j].hidden)))
                {
                  bulletsObjectArray[i].busy = false;
                  spaceDebrisArray[j].hidden = true;
                  M5.Lcd.fillRect(bulletsObjectArray[i].x, bulletsObjectArray[i].y, bulletsObjectArray[i].width, bulletsObjectArray[i].height, 0x0000);
                  M5.Lcd.fillRect(spaceDebrisArray[j].x, spaceDebrisArray[j].y, spaceDebrisArray[j].width, spaceDebrisArray[j].height, 0x0000);
                  score++;  
                  drawHealthAndScore();
                }
              }
            }
          }
        }
      }
      

      Если пуля соприкасается с космическим мусором, то счёт игры увеличиваем на единицу, состояние пули принимает значение busy = false, космический мусор становится скрытым hidden = true.

      Шаг 4. Получение данных с акселерометра

      Как было сказано раньше – акселерометр возвращает значения углов отклонения от осей "векторы наклона" (рис. 6).

      Рисунок 6. Векторы наклона акселерометра

      Прежде всего необходимо подключить библиотеку для работы с MPU9250:

      #include "utility/MPU9250.h"
      

      Объявим экземпляр класса:

      MPU9250 IMU;
      

      Произведён инициализацию и выполним калибровку датчика. Помните, что MPU9250 подключён к ESP32 через I2C интерфейс:

      void setup(){
      	M5.begin();
      	Wire.begin();
        	IMU.initMPU9250();
        	IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias);
      	// any actions	
      }
      

      Напишем функцию, которая принимает в качестве аргумента символ, соответствующий одной из осей 'X', 'Y' или 'Z'. Возвращает функция вектор наклона по оси. Если никаких данных не получено, то функция возвращает 0:

      int getAccel(char axis) {
        if (IMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)
        {
          IMU.readAccelData(IMU.accelCount);
          IMU.getAres();
          switch(axis)
          {
            case 'X':
              IMU.ax = (float)IMU.accelCount[0] * IMU.aRes * 1000;
              return IMU.ax;
            case 'Y':
              IMU.ax = (float)IMU.accelCount[1] * IMU.aRes * 1000;
              return IMU.ax;
            case 'Z':
              IMU.az = (float)IMU.accelCount[2] * IMU.aRes * 1000;
              return IMU.az;
          }
        }
        return 0;
      }
      

      Шаг 5. Двигаем космический корабль

      Функция чрезмерно простая, принцип работы мы рассмотрели на предыдущем шаге:

      bool moveCraft(int vector) {
        unsigned long millis_ = millis();
        if (((millis_ - moveCraft_pre_millis) >= moveCraft_time))
        {
          moveCraft_pre_millis = millis_;  
          int craft_x_pre = craft_x;
      
          if (abs(vector) > 70)
          {
            if (vector > 0)
              craft_x -= craft_step;
            else if (vector < 0)
              craft_x += craft_step;
            M5.Lcd.fillRect(craft_x_pre, craft_y, craft_width, craft_height, 0x0000);
          }
      
          if ((craft_x < (screen_width - craft_width - craft_step)) && (craft_x > craft_step))
          { 
            M5.Lcd.drawBitmap(craft_x, craft_y, craft_width, craft_height, (uint16_t *)craft);
          }
          else
          {
            return false;                                                                               
          }
        }
        return true;
      }
      

      Шаг 6. Игра окончена

      void gameOver() {
        M5.Lcd.fillScreen(0x0000);
        M5.Lcd.setTextSize(2);
        M5.Lcd.setTextColor(0x7bef);
        M5.Lcd.setCursor(35, 80);
        M5.Lcd.print("GAME OVER");
        M5.Lcd.setCursor(35, 110);
        M5.Lcd.print("score: ");
        M5.Lcd.print(score);
        M5.Lcd.setCursor(35, 140);
        M5.Lcd.print("please, press any key");  
        gameReset();
        while(true)
        {
          M5.update();
          if (M5.BtnA.wasPressed() || M5.BtnB.wasPressed() || M5.BtnC.wasPressed()) break;
        }
      }
      

      Шаг 7. Запуск!

      В целом это был очень интересный проект, посвященный работе с акселерометром на борту MPU9250. Давайте запустим и посмотрим, как это работает (рис. 7, 7.1.):

      Рисунок 7. Игровой процесс

      Рисунок. 7.1. Игра окончена

      Скачать

      • Видео с демонстрацией работы (YouTube): https://youtu.be/9gyiFfciUU4

      • Исходные коды (GitHub): https://github.com/dsiberia9s/SpaceDefense-m5stack

      • Конверторы изображений (Yandex Disk):https://yadi.sk/d/m2zvebPf3T5Zrc

      • Изображения (Yandex Disk): https://yadi.sk/d/JMMyPHOq3T5Zmf

      1 Reply Last reply Reply Quote 0
      • First post
        Last post