Pull to refresh

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

Reading time7 min
Views8.6K
Original author: RogueCode
Мы столкнулись с интересной статьей от разработчика приложений под Windows Phone и решили поделиться ею с вами.

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

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

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






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

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

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

Механизм:

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

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

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



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



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

Лабиринт:

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



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

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. Сигнальные провода идут к контактам ШИМ.

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

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



Код:

После добавления кода 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!
Tags:
Hubs:
Total votes 22: ↑19 and ↓3+16
Comments4

Articles

Information

Website
www.microsoft.com
Registered
Founded
Employees
1,001–5,000 employees
Location
Финляндия