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

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

    Scheduled Pinned Locked Moved Русскоязычный форум
    1 Posts 1 Posters 4.2k Views 2 Watching
    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 Offline
      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

      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

      With your input, this post could be even better 💗

      Register Login
      • First post
        Last post