Spisu treści:

Infinity Bike - gra wideo do treningu rowerowego w pomieszczeniu: 5 kroków
Infinity Bike - gra wideo do treningu rowerowego w pomieszczeniu: 5 kroków

Wideo: Infinity Bike - gra wideo do treningu rowerowego w pomieszczeniu: 5 kroków

Wideo: Infinity Bike - gra wideo do treningu rowerowego w pomieszczeniu: 5 kroków
Wideo: ILU POLICJANTÓW POTRZEBA ABY OBEZWŁADNIĆ OJCA BEZ SPRZĘTU 2024, Wrzesień
Anonim
Image
Image
Materiały
Materiały

Podczas sezonów zimowych, chłodnych dni i złej pogody miłośnicy rowerzystów mają tylko kilka możliwości uprawiania ulubionego sportu. Szukaliśmy sposobu, aby trening w pomieszczeniu z konfiguracją roweru/trenera był nieco ciekawszy, ale większość dostępnych produktów jest albo droga, albo po prostu nudna w użyciu. Dlatego zaczęliśmy rozwijać Infinity Bike jako szkoleniową grę wideo typu Open Source. Rower Infinity odczytuje prędkość i kierunek z roweru i oferuje poziom interaktywności, którego nie można łatwo znaleźć w trenażerach rowerowych.

Wykorzystujemy prostotę mikrokontrolera Arduino oraz kilka części drukowanych w 3D, aby przymocować niedrogie czujniki do roweru zamontowanego na trenażerze. Informacje są przekazywane do gry wideo stworzonej za pomocą popularnego silnika do tworzenia gier, Unity. Pod koniec tej instrukcji powinieneś być w stanie skonfigurować własne czujniki na swoim rowerze i przesłać informacje z czujników do Unity. Zawarliśmy nawet tor, po którym możesz jeździć i testować swój nowy zestaw. Jeśli jesteś zainteresowany współpracą, możesz sprawdzić nasz GitHub.

Krok 1: Materiały

Materiały
Materiały

Lista materiałów, których będziesz potrzebować, może się nieco różnić; dla

Na przykład, rozmiar twojego roweru będzie dyktować długości kabli rozruchowych, których potrzebujesz, ale oto główne części, których będziesz potrzebować. Prawdopodobnie można znaleźć tańsze ceny za każdy element na stronie internetowej, takiej jak AliExpress, ale czekanie 6 miesięcy na wysyłkę nie zawsze jest opcją, więc używaliśmy nieco droższych części, więc oszacowanie nie jest przekrzywione.

1 x Arduino nano (22,00 USD)

1 x Mini Breadboard (1,33 USD/szt.)

1 x rezystor 220 Ohm (1,00 USD/zestaw)

1 x potencjometr 10K (1,80 USD/szt.)

1 x czujnik Halla (0,96 USD)

Pasek rozrządu do drukarki 3D 20 cm x 6 mm (3,33 USD)

1 zestaw x różne długości śrub i śrub M3 (6,82 USD)

1 x magnes prędkościomierza rowerowego (0,98 USD)

Powyższy materiał zamontowaliśmy za pomocą części wydrukowanych w 3D. Pliki, których użyliśmy, są wymienione poniżej i są ponumerowane w tej samej konwencji, co obrazek na początku tej sekcji. Wszystkie pliki można znaleźć na Thingiverse. Możesz ich używać bez zmian, ale upewnij się, że wymiary, których użyliśmy, pasują do Twojego roweru.

1. FrameConnection_PotentiometerHolder_U_Holder.stl

2. FrameConnection_Spacer.stl

3. BreadboardFrameHolder.stl

4. Koło pasowe_Potencjometr Side.stl

5. Pot_PulleyConnection.stl

6. FrameConnection.stl

7. Pulley_HandleBarSide_Print2.stl

8. FrameToHallSensorConnector.stl

9. PotHolder.stl

10. HallSensorAttach.stl

Krok 2: Odczytywanie i przesyłanie danych do Unity

Odczytywanie i przesyłanie danych do Unity
Odczytywanie i przesyłanie danych do Unity

Kod Arduino i Unity będą współpracować, aby zebrać, przesyłać i przetwarzać dane z czujników na rowerze. Unity zażąda wartości z Arduino, wysyłając ciąg przez port szeregowy i poczeka, aż Arduino odpowie z żądanymi wartościami.

Najpierw przygotowujemy Arduino z biblioteką Serial Command, która służy do zarządzania żądaniami z Unity poprzez parowanie ciągu żądania z funkcją. Podstawową konfigurację tej biblioteki można wykonać w następujący sposób;

#include "SerialCommand.h"

Polecenie szeregowe sCmd; void setup() { sCmd.addCommand("TRIGG", TriggHanlder); Serial.początek(9600); } void loop() { while (Serial.available() > 0) { sCmd.readSerial(); } } void TriggHandler () { /*Odczytaj i prześlij czujniki tutaj*/ }

Funkcja TriggHandler jest dołączona do obiektu SCmd. Jeśli serial otrzyma ciąg pasujący do dołączonego polecenia (w tym przypadku TRIGG), wykonywana jest funkcja TriggHandler.

Używamy potencjometru do pomiaru kierunku skrętu oraz czujnika Halla do pomiaru obrotów roweru na minutę. Odczyty z potencjometru można łatwo wykonać za pomocą wbudowanych funkcji Arduino. Funkcja TriggHandler może następnie wydrukować wartość do numeru seryjnego z następującą zmianą.

nieważny TriggHandler (){

/*Odczytywanie wartości potencjometru*/ Serial.println(analogRead(ANALOGPIN)); }

Czujnik Halla ma nieco więcej ustawień, zanim będziemy mogli uzyskać przydatne pomiary. W przeciwieństwie do potencjometru chwilowa wartość czujnika Halla nie jest zbyt użyteczna. Ponieważ próbowaliśmy zmierzyć prędkość koła, interesował mnie czas między spustami.

Każda funkcja użyta w kodzie Arduino wymaga czasu, a jeśli magnes zrówna się z czujnikiem Halla w niewłaściwym czasie, pomiar może zostać w najlepszym razie opóźniony lub w najgorszym całkowicie pominięty. Jest to oczywiście złe, ponieważ Arduino może zgłaszać prędkość DUŻO różną od rzeczywistej prędkości koła.

Aby tego uniknąć, używamy funkcji Arduinos zwanej attach interrupt, która pozwala nam wyzwalać funkcję za każdym razem, gdy wyznaczony pin cyfrowy jest wyzwalany rosnącym sygnałem. Funkcja rpm_fun jest dołączona do przerwania z pojedynczym wierszem kodu dodanym do kodu konfiguracji.

pusta konfiguracja (){

sCmd.addCommand("TRIGG", TriggHanlder); attachInterrupt(0, rpm_fun, ROSING); Serial.początek(9600); } //Funkcja rpm_fun służy do obliczania prędkości i jest zdefiniowana jako; unsigned long lastRevolTime = 0; unsigned long revolSpeed = 0; void rpm_fun() { unsigned long revolTime = millis(); unsigned long deltaTime = revolTime - lastRevolTime; /*revolSpeed to wartość przesyłana do kodu Arduino*/ revolSpeed = 20000 / deltaTime; lastRevolTime =RevolTime; } TriggHandler może następnie przesłać resztę informacji na żądanie. void TriggHanlder () { /*Odczytanie wartości potencjometru*/ Serial.println(analogRead(ANALOGPIN)); Serial.println(revolSpeed); }

Mamy teraz wszystkie bloki konstrukcyjne, których można użyć do zbudowania kodu Arduino, który będzie przesyłał dane przez port szeregowy, gdy żądanie zostanie wysłane przez Unity. Jeśli chcesz mieć kopię pełnego kodu, możesz pobrać ją z naszego GitHub. Aby sprawdzić, czy kod został prawidłowo skonfigurowany, możesz użyć monitora szeregowego do wysłania TRIGG; upewnij się, że ustawiłeś linię kończącą się na Zwrot karetki. W następnej sekcji skupimy się na tym, w jaki sposób nasze skrypty Unity mogą żądać i otrzymywać informacje z Arduino.

Krok 3: Odbieranie i przetwarzanie danych

Odbieranie i przetwarzanie danych
Odbieranie i przetwarzanie danych

Unity to świetne oprogramowanie dostępne za darmo dla hobbystów

zainteresowany tworzeniem gier; zawiera wiele funkcji, które mogą naprawdę skrócić czas konfigurowania niektórych rzeczy, takich jak wątki lub programowanie GPU (cieniowanie AKA), bez ograniczania tego, co można zrobić za pomocą skryptów C#. Mikrokontrolery Unity i Arduino mogą być używane razem, aby tworzyć unikalne interaktywne doświadczenia przy stosunkowo niewielkim budżecie.

Celem tej instrukcji jest pomoc w konfiguracji komunikacji między Unity a Arduino, więc nie zagłębimy się zbyt głęboko w większość funkcji dostępnych w Unity. Istnieje wiele WSPANIAŁYCH samouczków dla jedności i niesamowita społeczność, która mogłaby znacznie lepiej wyjaśnić, jak działa Unity. Istnieje jednak specjalna nagroda dla tych, którym uda się przebrnąć przez tę instrukcję, która służy jako mała prezentacja tego, co można zrobić. Możesz pobrać na naszego Github naszą pierwszą próbę stworzenia toru z realistyczną fizyką roweru.

Najpierw przejdźmy przez absolutne minimum, które należy wykonać, aby komunikować się z Arduino przez port szeregowy. Szybko okaże się, że ten kod nie nadaje się do rozgrywki, ale dobrze jest przejść przez każdy krok i poznać ograniczenia.

W Unity utwórz nową scenę z pojedynczym pustym obiektem GameObject o nazwie ArduinoReceive i dołącz skrypt C# o nazwie ArduinoReceive. W tym skrypcie dodamy cały kod obsługujący komunikację z Arduino.

Istnieje biblioteka, do której należy mieć dostęp, zanim będziemy mogli komunikować się z portami szeregowymi twojego komputera; Unity musi być skonfigurowany, aby umożliwić korzystanie z niektórych bibliotek. Przejdź do Edycja->ProjectSerring->Player i obok Poziomu zgodności Api w obszarze Konfiguracja przełącznika. NET 2.0 Podzbiór na. NET 2.0. Teraz dodaj następujący kod na górze skryptu;

za pomocą System. IO. Ports;

Umożliwi to dostęp do klasy SerialPort, którą można zdefiniować jako obiekt klasy ArduinoReceive. Ustaw jako prywatny, aby uniknąć zakłóceń z innego skryptu.

prywatny SerialPort arduinoPort;

Obiekt arduinoPort można otworzyć wybierając odpowiedni port (np. w którym USB jest podłączony Arduino) oraz prędkość transmisji (czyli prędkość, z jaką przesyłane są informacje). Jeśli nie masz pewności, do którego portu jest podłączone Arduino, możesz dowiedzieć się tego w menedżerze urządzeń lub otwierając Arduino IDE. W przypadku szybkości transmisji domyślna wartość na większości urządzeń to 9600, po prostu upewnij się, że masz tę wartość w kodzie Arduino i powinna działać.

Kod powinien teraz wyglądać tak;

za pomocą System. Collections;

za pomocą System. Collections. Generic; za pomocą UnityEngine; za pomocą System. IO. Ports; public class ArduinoReceive: MonoBehaviour { private SerialPort arduinoPort; // Użyj tego do inicjalizacji void Start() { arduinoPort = new SerialPort("COM5", 9600); arduinoPort. Open(); WriteToArduino("TRIGG"); } }

Twój numer COM najprawdopodobniej będzie inny. Jeśli korzystasz z komputera MAC, nazwa COM może mieć nazwę podobną do tej /dev/cu.wchusbserial1420. Upewnij się, że kod z sekcji 4 jest przesłany do Arduino, a monitor szeregowy jest zamknięty do końca tej sekcji i że ten kod kompiluje się bez problemu.

Teraz wyślijmy żądanie do Arduino co klatkę i zapiszmy wyniki w oknie konsoli. Dodaj funkcję WriteToArduino do klasy ArduinoReceive. Powrót karetki i nowa linia są niezbędne, aby kod Arduino prawidłowo przeanalizował przychodzącą instrukcję.

private void WriteToArduino(string message)

{ wiadomość = wiadomość + "\r\n"; arduinoPort. Write(wiadomość); arduinoPort. BaseStream. Flush (); }

Tę funkcję można następnie wywołać w pętli Update.

nieważna aktualizacja ()

{ WriteToArduino ("TRIGG"); Debug. Log("Pierwsza wartość: " + arduinoPort. ReadLine()); Debug. Log("Druga wartość: " + arduinoPort. ReadLine()); }

Powyższy kod to absolutne minimum potrzebne do odczytania danych z Arduino. Jeśli zwrócisz szczególną uwagę na FPS podany przez jedność, powinieneś zauważyć znaczny spadek wydajności. W moim przypadku przechodzi z około 90 FPS bez odczytu/zapisu do 20 FPS. Jeśli Twój projekt nie wymaga częstych aktualizacji, może wystarczyć, ale w przypadku gry wideo 20 FPS to zdecydowanie za mało. W następnej sekcji omówimy, w jaki sposób można poprawić wydajność za pomocą wielowątkowości.

Krok 4: Optymalizacja transferu danych

W poprzedniej sekcji omówiono, jak skonfigurować podstawowe

komunikacja między programem Arduino i Unity. Głównym problemem z tym kodem jest wydajność. W swojej obecnej implementacji Unity musi czekać, aż Arduino otrzyma, przetworzy i odpowie na żądanie. W tym czasie kod Unity musi czekać na wykonanie żądania i nie robi nic więcej. Rozwiązaliśmy ten problem, tworząc wątek, który będzie obsługiwał żądania i przechowuje zmienną w głównym wątku.

Aby rozpocząć, musimy dołączyć bibliotekę wątków, dodając;

za pomocą System. Threading;

Następnie ustawiamy funkcję, którą uruchamiamy w wątkach. AsynchronousReadFromArduino rozpoczyna się od wypisania żądania do Arduino za pomocą funkcji WrtieToArduino. Odczyt jest zawarty w bloku try-catch, jeśli upłynął limit czasu odczytu, zmienne pozostają puste, a funkcja OnArduinoInfoFail jest wywoływana zamiast OnArduinoInfoReceive.

Następnie definiujemy funkcje OnArduinoInfoFail i OnArduinoInfoReceive. W tym celu drukujemy wyniki do konsoli, ale możesz przechowywać wyniki w zmiennych, których potrzebujesz dla swojego projektu.

private void OnArduinoInfoFail()

{ Debug. Log("Czytanie nie powiodło się"); } private void OnArduinoInfoReceived(rotacja ciągu, szybkość ciągu) { Debug. Log("Readin pomyślnie"); Debug. Log("Pierwsza wartość: " + obrót); Debug. Log("Druga wartość: " + prędkość); }

Ostatnim krokiem jest uruchomienie i zatrzymanie wątków, które zażądają wartości z Arduino. Musimy upewnić się, że ostatni wątek jest wykonany z ostatnim zadaniem przed rozpoczęciem nowego. W przeciwnym razie do Arduino może zostać wysłanych wiele żądań w tym samym czasie, co może zmylić Arduino/Unity i dać nieprzewidywalne wyniki.

Wątek prywatny activeThread = null;

void Update() { if (activeThread == null || !activeThread. IsAlive) { activeThread = new Thread(AsynchronousReadFromArduino); aktywnyWątek. Start(); } }

Jeśli porównasz wydajność kodu z tym, który napisaliśmy w punkcie 5, wydajność powinna znacznie się poprawić.

private void OnArduinoInfoFail()

{ Debug. Log("Czytanie nie powiodło się"); }

Krok 5: Gdzie dalej?

Gdzie dalej?
Gdzie dalej?

Przygotowaliśmy demo, które można pobrać na naszego Github (https://github.com/AlexandreDoucet/InfinityBike), pobrać kod i grę oraz przejechać się po naszym torze. Wszystko jest przygotowane do szybkiego treningu i mamy nadzieję, że da ci przedsmak tego, co możesz zbudować, jeśli użyjesz tego, czego cię nauczyliśmy w tej instrukcji.

Kredyty

Współtwórcy projektu

Alexandre Doucet (_Doucet_)

Maxime Boudreau (MxBoud)

Zasoby zewnętrzne [silnik gry Unity](https://unity3d.com)

Ten projekt rozpoczął się po przeczytaniu samouczka Allana Zucconi „jak zintegrować Arduino z Unity” (https://www.alanzucconi.com/2015/10/07/how-to-int…)

Żądania z Arduino obsługiwane są za pomocą biblioteki SerialCommand (https://github.com/kroimon/Arduino-SerialCommand)

Zalecana: