Разработка игры под управлением WP8 с использованием Netduino

в 14:04, , рубрики: mazegame, netduino, wp8, Блог компании Nokia

Мы столкнулись с интересной статьей от разработчика приложений под Windows Phone и решили поделиться ею с вами.

Когда я был маленьким, родители подарили мне деревянную игру-лабиринт. Она мне очень нравилась. Не думаю, что меня когда-либо волновал сам лабиринт, но механизм наклона был интригующим и очень простым.

Позже я вспомнил об этой игре и решил найти эту игру онлайн и свой старый лабиринт в кладовке.

Сейчас существует масса схожих игр, использующих акселерометр телефона, но их графика оставляет желать лучшего. Вот почему я сделал фотореалистичный вариант ;-)


Разработка игры под управлением WP8 с использованием Netduino

Цель была сделать лабиринт, соединяющий в себе олдскульный и современный вариант игры. Так что я использовал телефон для управления наклоном физического лабиринта через Bluetooth с Netduino. Лабиринт — это простая модель, распечатанная на 3D принтере и наклоняемая двумя сервоприводами. Бонусом от добавления выключателя в конце послужила обратная связь с телефоном, дающая знать о завершения игры.

Что нам понадобится:

• 2 довольно сильных сервопривода. Я использовал Turnigy TGY-9018MG Metal Gear Servo из HobbyKing
• Модуль Bluetooth
• Netduino
• Небольшой квадратный лабиринт
• Резистор на 10К Ом
• Шарик из проводящего материала

Механизм:

Прежде, чем вдаваться в технические подробности, стоит разобраться в наклоняющем механизме. Описание ниже может поставить в тупик, поскольку его сложно объяснить. Так что если почувствуете, что запутались, смотрите на картинки чуть ниже, все станет более понятно.

Обычно, чтобы реализовать наклон в двух направлениях, делают по аналогии с деревянной игрой. Один сервопривод закрепляется к внутренней части игрушки, затем второй сервопривод крепится к внешнему корпусу.

Стоит заметить, что в деревянном лабиринте целых три коробки помещены одна в другую, как это можно увидеть на первой картинке поста. Я пользовался той же теорией, но несколько её упростил. Нижняя часть обеих осей не будет действительно горизонтальной, так что лабиринт по сути будет вращаться вокруг условного внутреннего стержня.

Так что мы просто имеем два сервопривода, расположенных под углом 90 градусов относительно друг друга. Первый будет вращать второй. А второй, в свою очередь, будет вращать подставку, прикрепленную к самому лабиринту. Ниже две картинки, иллюстрирующие вышеописанное.

Разработка игры под управлением WP8 с использованием NetduinoРазработка игры под управлением WP8 с использованием Netduino

И, чтобы стало еще понятнее, вот как это выглядит в действии:

Чтобы соединить приводы я просто туго стянул их резинкой. Нижний сервопривод я приклеил к старому креплению от привода и закрепил на плоском основании.

Лабиринт:

С моделью лабиринта пришлось немного помучиться, но, так или иначе, модель в конце поста вполне рабочая, её при желании можно распечатать на собственном принтере. Я распечатал его в размере 9 на 9 см, т.к. это самый большой размер, который может распечатать мой Makerbot Thing-O-Matic.

Разработка игры под управлением WP8 с использованием Netduino

Чтобы создать модель, я первым делом отправился на www.mazegenerator.net/ и создал там лабиринт 9х9. Затем импортировал результат в SketchUp, отрисовал линии и нарастил стенки. Стенки я сделал высокими настолько, чтобы шарик не вываливался из лабиринта, но и не застревал.

Netduino:

Электронная часть не так уж сложна.

Разработка игры под управлением WP8 с использованием Netduino

Примечание: провода заземления расположены так, чтобы быть более заметными.

Модуль Bluetooth:

В данном случае он присоединяется также, как и в моих предыдущих проектах. Код точно такой же, так что подробности можно почитать здесь:
blog.roguecode.co.za/Post/ControllingaNetduinooverBluetoothwithWP8
blog.roguecode.co.za/Post/MoreNetduino%2bWP8%2bBluetoothfun-3Dreconstruction
blog.roguecode.co.za/Post/Netduino%2bSonar%2bWP8%2bBluetooth-Controllingsoundwithyourmind

Приводы:

Я начал этот раздел с прямого подключения сервоприводов к разъёму питания Netduino. Понимаю, что вообще-то это не рекомендуется делать, поскольку это приводит к большим затратам энергии, но для запуска концепта это должно быть приемлемо.

Кроме того, у меня возникли другие проблемы, кроме потребляемой мощности – как только стартовали сервоприводы, происходило отключение модуля Bluetooth. Это вполне объяснимо шумом/помехами, но я не сталкивался с тем, чтобы это влияло на какой-либо другой компонент так явно.

Так что я подсоединил их к батарейному блоку на 4.8V. Важно помнить, что необходимо соединить заземление батареи и Netduino. Сигнальные провода идут к контактам ШИМ.

Конечная точка лабиринта:

Чтобы сделать контакты конечной точки лабиринта, я отрезал две металлические полоски и поместил одну горизонтально в конечной точке, а вторую – вертикально, с небольшим зазором относительно первой. Когда шарик достигает конца лабиринта, он замыкает контакт между двумя площадками и активирует выключатель.

Разработка игры под управлением WP8 с использованием Netduino

Код:

После добавления кода Bluetooth (по ссылкам выше) остальное становится довольно просто.

static SerialPort bt;
      static string buffer = "";
      static Servo servoX;
      static Servo servoY;
      static InterruptPort endStopPort;
      static bool isRunning = false;
      public static void Main()
      {
          servoX = new Servo(Pins.GPIO<em>PIN</em>D5);
          servoY = new Servo(Pins.GPIO<em>PIN</em>D9);
          bt = new SerialPort(SerialPorts.COM1, 9600, Parity.None, 8, StopBits.One);
          bt.Open();
          endStopPort = new InterruptPort(Pins.GPIO<em>PIN</em>D10, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLevelHigh);
          endStopPort.OnInterrupt += new NativeEventHandler(endStopPort<em>OnInterrupt);
      bt.DataReceived += new SerialDataReceivedEventHandler(bt</em>DataReceived);
          servoX.Degree = 90;
          servoY.Degree = 105;
          while (true)
          {
              //do some other stuff here
              Thread.Sleep(1000);
          }
      }
  static void endStopPort_OnInterrupt(uint data1, uint data2, DateTime time)
      {
          if (isRunning)
          {
              isRunning = false;
              byte[] bytes = Encoding.UTF8.GetBytes("done|");
              bt.Write(bytes, 0, bytes.Length);
          }
          Thread.Sleep(1);
          endStopPort.ClearInterrupt();
      }

Для начала мы устанавливаем оба сервопривода, Bluetooth и выключатель на контактной площадке конца лабиринта. Чтобы разобраться, как и почему работают выключатели на Netduino, сходите по ссылке. Затем выравниваем сервоприводы, чтобы лабиринт был расположен горизонтально.

В идеальном мире, в котором они держатся не на резинках, оба значения будут 90 градусов. Класс приводов взят здесь.

Код в обработчике событий заставит выключатель срабатывать, когда шарик замкнет контакты. Но, поскольку мы не хотим, чтобы это срабатывало раз за разом, мы убедимся, что текущая игра запущена (с помощью isRunning bool).

В заключительной части кода Netduino обрабатывает сообщения от телефона.

private static void DoSomething(string buffer)
        {
            if (buffer == "start")
            {
                isRunning = true;
            }
            else
            {
                string[] split = buffer.Split(new char[] { ',' });
                if (split.Length == 2)
                {
                    int x = int.Parse(split[0]);
                    int y = int.Parse(split[1]);
                    servoX.Degree = x + 3;
                    servoY.Degree = y + 12;
                    Debug.Print(x + " " + y);
                }
            }
        }

Когда игрок нажимает кнопку GO на телефоне, по каналу Bluetooth посылается команда «start». Так что мы устанавливаем bool в значение true, чтобы показать, что игра началась.

Если сообщение – не сообщение о старте, то мы знаем, что это значения акселерометра. Как будет понятно из телефонной части кода, мы отправляем эти данные как значения по осям X, Y. Код разделяет их, затем конвертирует в значения int и устанавливает сервоприводы. Как упоминалось ранее, я добавляю небольшое смещение, поскольку мои приводы не до конца откалиброваны по уровню.

WP8:

Телефонный код совершенно прост. Вот базовые функции, которые он выполняет:

— Послать «start», когда нажата кнопка GO
— Отобразить таймер
— Отправить значения акселерометра по осям X и Y
— Отобразить окончательное время игры при получении «done»

И вот код для каждой из них:

Послать «start», когда нажата кнопка GO и отобразить таймер

private void goBtn<em>Click</em>1(object sender, RoutedEventArgs e)
{
    Write("start");
    secTxt.Text = "";
    msText.Text = "";
    <em>startedDT = DateTime.Now;
    TimeSpan timeTaken;
    _timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 51) };
    _timer.Tick += (s, ev) =>
        {
            timeTaken = DateTime.Now.Subtract(</em>startedDT);
            secTxt.Text = timeTaken.Seconds.ToString();
            msText.Text = timeTaken.Milliseconds.ToString();
        };
    _timer.Start();
    goBtn.Visibility = System.Windows.Visibility.Collapsed;
    stopBtn.Visibility = System.Windows.Visibility.Visible;
    timerDisplayContainer.Visibility = System.Windows.Visibility.Visible;
}

Следует отметить, что использовать DispatcherTimer с такой высокой частотой, вероятно, не самая лучшая идея. И он не должен использоваться кроме случаев, когда это действительно необходимо.

Отправить значения акселерометра по осям X и Y

 void <em>acc</em>ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args)
        {
            Write(Convert(args.Reading.AccelerationX) + "," + Convert(-args.Reading.AccelerationY) + "|");
            Dispatcher.BeginInvoke(() =>
                {
                    xRight.Opacity = args.Reading.AccelerationX * 2;
                    xLeft.Opacity = -args.Reading.AccelerationX * 2;
                    yTop.Opacity = args.Reading.AccelerationY * 2;
                    yBottom.Opacity = -args.Reading.AccelerationY * 2;
                });
        }
private string Convert(double val)
        {
            return ((int)((Clamp((val * 2d), -1, 1) * 10) + 90)).ToString();
            //first double it so full range is -45deg to 45deg
        //clamp the above value to the max of -1 and 1
            //then multiple by the max angle we want the servos to goto
            //then add 90 because servos go from 0 to 180, not -90 to 90
        }
private double Clamp(double value, double min, double max)
        {
            return value < min ? min : value > max ? max : value;
        }

Те, кто использовал MathHelper.Clamp в XNA должны узнать функционал. Он просто останавливает значение от превышения или снижения вне установленного лимита. Код внутри Dispatcher делает несколько визуальных действий, чтобы показать угол наклона в UI.

Показ финального времени после получения “done”.

private void DoSomethingWithReceivedString(string <em>receivedBuffer)
{
    if (</em>receivedBuffer == "done")
    {
        <em>timer.Stop();
        stopBtn.Visibility = System.Windows.Visibility.Collapsed;
        goBtn.Visibility = System.Windows.Visibility.Visible;
        TimeSpan timeTaken = DateTime.Now.Subtract(</em>startedDT);
        MessageBox.Show(string.Format("You took {0}:{1}", timeTaken.Seconds, timeTaken.Milliseconds), "Done!", MessageBoxButton.OK);
    }
}

Вот и все, по сути. Смотрите исходники для примеров BT и UI.

Загрузки:

В zip-файле содержатся решение для Netduino, решение для WP8 и STL-модель для распечатывания.

Поделитесь вашими проектами, в которых использовался WP8!

Автор: nokiaman

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js