Mục lục:

Infinity Bike - Trò chơi video đào tạo xe đạp trong nhà: 5 bước
Infinity Bike - Trò chơi video đào tạo xe đạp trong nhà: 5 bước

Video: Infinity Bike - Trò chơi video đào tạo xe đạp trong nhà: 5 bước

Video: Infinity Bike - Trò chơi video đào tạo xe đạp trong nhà: 5 bước
Video: Nấm lùn chạy xe 2024, Tháng sáu
Anonim
Image
Image
Vật liệu
Vật liệu

Trong mùa đông, những ngày lạnh giá và thời tiết xấu, những người đam mê đi xe đạp chỉ có một số lựa chọn để rèn luyện sức khỏe với môn thể thao yêu thích của họ. Chúng tôi đang tìm cách làm cho việc tập luyện trong nhà với thiết lập xe đạp / huấn luyện viên trở nên thú vị hơn một chút nhưng hầu hết các sản phẩm có sẵn đều đắt tiền hoặc chỉ đơn giản là nhàm chán khi sử dụng. Đây là lý do tại sao chúng tôi bắt đầu phát triển Infinity Bike như một trò chơi video đào tạo Mã nguồn mở. Xe đạp vô cực đọc tốc độ và hướng từ xe đạp của bạn và cung cấp mức độ tương tác không thể dễ dàng tìm thấy với người tập xe đạp.

Chúng tôi tận dụng sự đơn giản có sẵn từ bộ vi điều khiển Arduino và một số bộ phận được in 3D để đảm bảo các cảm biến rẻ tiền cho một chiếc xe đạp gắn trên xe tập. Thông tin được chuyển tiếp đến một trò chơi điện tử được tạo bằng công cụ tạo trò chơi phổ biến, Unity. Vào cuối phần hướng dẫn này, bạn sẽ có thể thiết lập các cảm biến của riêng mình trên xe đạp của mình và chuyển thông tin của các cảm biến đến Unity. Chúng tôi thậm chí còn bao gồm một đường đi mà bạn có thể đi cùng và thử nghiệm thiết lập mới của mình. Nếu bạn quan tâm đến việc đóng góp, bạn có thể xem GitHub của chúng tôi.

Bước 1: Vật liệu

Vật liệu
Vật liệu

Danh sách vật liệu bạn cần có thể thay đổi một chút; vì

Ví dụ, kích thước của chiếc xe đạp của bạn sẽ quy định độ dài của dây cáp nhảy mà bạn cần nhưng đây là những bộ phận chính bạn sẽ cần. Bạn có thể tìm thấy giá rẻ hơn cho mỗi món hàng trên trang web như AliExpress nhưng chờ đợi 6 tháng để vận chuyển không phải lúc nào cũng là một lựa chọn, vì vậy bạn đã sử dụng các bộ phận đắt hơn một chút để ước tính không bị sai lệch.

1 x Arduino nano ($ 22,00)

1 x Mini Breadboard ($ 1,33 / chiếc)

Điện trở 1 x 220 Ohm ($ 1,00 / bộ)

Chiết áp 1 x 10K ($ 1,80 / chiếc)

1 x cảm biến Hall ($ 0,96)

Dây đai thời gian máy in 3D 20 cm x 6 mm ($ 3,33)

1 bộ x Nhiều vít và bu lông Lenght M3 ($ 6,82)

1 x nam châm đồng hồ tốc độ xe đạp ($ 0,98)

Chúng tôi đã gắn vật liệu ở trên với các bộ phận được in 3D. Các tệp chúng tôi đã sử dụng được liệt kê dưới đây và chúng được đánh số theo quy ước giống như hình ảnh ở đầu phần này. Tất cả các tệp có thể được tìm thấy trên Thingiverse. Bạn có thể sử dụng chúng như hiện tại nhưng hãy đảm bảo rằng kích thước chúng tôi đã sử dụng phù hợp với xe đạp của bạn.

1. FrameConnection_PotentiometerHolder_U_Holder.stl

2. FrameConnection_Spacer.stl

3. BreadboardFrameHolder.stl

4. Pulley_PotentiometerSide.stl

5. Pot_PulleyConnection.stl

6. FrameConnection.stl

7. Pulley_HandleBarSide_Print2.stl

8. FrameToHallSensorConnector.stl

9. PotHolder.stl

10. HallSensorAttach.stl

Bước 2: Đọc và chuyển dữ liệu sang Unity

Đọc và truyền dữ liệu sang Unity
Đọc và truyền dữ liệu sang Unity

Mã Arduino và Unity sẽ làm việc cùng nhau để thu thập, truyền và xử lý dữ liệu từ các cảm biến trên xe đạp. Unity sẽ yêu cầu giá trị từ Arduino bằng cách gửi một chuỗi thông qua nối tiếp và đợi Arduino phản hồi với các giá trị được yêu cầu.

Đầu tiên, chúng tôi chuẩn bị Arduino với Thư viện Lệnh nối tiếp được sử dụng để quản lý các yêu cầu từ Unity bằng cách ghép nối một chuỗi yêu cầu với một hàm. Một thiết lập cơ bản cho thư viện này có thể được thực hiện như sau;

#include "SerialCommand.h"

SerialCommand sCmd; void setup () {sCmd.addCommand ("TRIGG", TriggHanlder); Serial.begin (9600); } void loop () {while (Serial.available ()> 0) {sCmd.readSerial (); }} void TriggHandler () {/ * Đọc và truyền các cảm biến tại đây * /}

Hàm TriggHandler được gắn vào đối tượng SCmd. Nếu nối tiếp nhận được một chuỗi khớp với lệnh đính kèm (trong trường hợp này là TRIGG), hàm TriggHandler được thực thi.

Chúng tôi sử dụng chiết áp để đo hướng lái và cảm biến sảnh để đo vòng quay trên phút của xe đạp. Các bài đọc từ chiết áp có thể dễ dàng thực hiện bằng cách sử dụng các chức năng tích hợp từ Arduino. Sau đó, hàm TriggHandler có thể in giá trị vào chuỗi với sự thay đổi sau.

void TriggHandler () {

/ * Đọc giá trị của chiết áp * / Serial.println (analogRead (ANALOGPIN)); }

Cảm biến Hall phải thiết lập nhiều hơn một chút trước khi chúng ta có thể có các phép đo hữu ích. Trái ngược với chiết áp, giá trị tức thời của cảm biến sảnh không hữu ích lắm. Vì đang cố gắng đo tốc độ của bánh xe, nên thời gian giữa các lần kích hoạt là điều được quan tâm.

Mọi chức năng được sử dụng trong mã Arduino đều cần thời gian và nếu nam châm kết nối với cảm biến Hall không đúng thời điểm, phép đo có thể bị trì hoãn ở mức tốt nhất hoặc bị bỏ qua hoàn toàn. Điều này rõ ràng là không tốt vì Arduino có thể báo cáo tốc độ khác RẤT NHIỀU so với tốc độ thực của bánh xe.

Để tránh điều này, chúng tôi sử dụng một tính năng của Arduinos được gọi là ngắt đính kèm, cho phép chúng tôi kích hoạt một chức năng bất cứ khi nào một chân kỹ thuật số được chỉ định được kích hoạt với tín hiệu tăng. Hàm rpm_fun được gắn vào một ngắt với một dòng mã được thêm vào mã thiết lập.

void setup () {

sCmd.addCommand ("TRIGG", TriggHanlder); mountInterrupt (0, rpm_fun, RISING); Serial.begin (9600); } // Hàm rpm_fun được sử dụng để tính toán tốc độ và được định nghĩa là; unsigned long lastRevolTime = 0; unsigned long Revpeed = 0; void rpm_fun () {unsigned long revTime = millis (); unsigned long deltaTime = Revitime - lastRevolTime; / * revSpeed là giá trị được truyền tới mã Arduino * / revSpeed = 20000 / deltaTime; lastRevolTime = thời gian quay lại; } TriggHandler sau đó có thể truyền phần còn lại của thông tin khi được yêu cầu. void TriggHanlder () {/ * Đọc giá trị của chiết áp * / Serial.println (analogRead (ANALOGPIN)); Serial.println (Revpeed); }

Bây giờ chúng ta có tất cả các khối xây dựng có thể được sử dụng để xây dựng mã Arduino, mã này sẽ truyền dữ liệu qua chuỗi nối tiếp khi Unity đưa ra yêu cầu. Nếu bạn muốn có một bản sao của mã đầy đủ, bạn có thể tải xuống trên GitHub của chúng tôi. Để kiểm tra xem mã có được thiết lập đúng cách hay không, bạn có thể sử dụng màn hình nối tiếp để gửi TRIGG; hãy đảm bảo rằng bạn đặt dòng kết thúc thành Chuyển hàng trở lại. Phần tiếp theo sẽ tập trung vào cách các tập lệnh Unity của chúng tôi có thể yêu cầu và nhận thông tin từ Arduino.

Bước 3: Nhận và xử lý dữ liệu

Nhận và xử lý dữ liệu
Nhận và xử lý dữ liệu

Unity là một phần mềm tuyệt vời có sẵn miễn phí cho người có sở thích

quan tâm đến việc làm trò chơi; nó đi kèm với một số lượng lớn các chức năng thực sự có thể cắt giảm thời gian thiết lập một số thứ như phân luồng hoặc lập trình GPU (đổ bóng AKA) mà không hạn chế những gì có thể thực hiện với các tập lệnh C #. Bộ vi điều khiển Unity và Arduino có thể được sử dụng cùng nhau để tạo ra những trải nghiệm tương tác độc đáo với ngân sách tương đối nhỏ.

Trọng tâm của tài liệu hướng dẫn này là giúp thiết lập giao tiếp giữa Unity và Arduino, vì vậy chúng tôi sẽ không đi sâu vào hầu hết các tính năng có sẵn với Unity. Có rất nhiều hướng dẫn TUYỆT VỜI về sự thống nhất và một cộng đồng đáng kinh ngạc có thể làm tốt hơn nhiều việc giải thích cách Unity hoạt động. Tuy nhiên, có một giải thưởng đặc biệt dành cho những người cố gắng làm việc theo cách của họ thông qua tài liệu hướng dẫn này để giới thiệu một chút về những gì có thể làm được. Bạn có thể tải xuống trên Github của chúng tôi, nỗ lực đầu tiên của chúng tôi là tạo một đường đua với vật lý xe đạp thực tế.

Đầu tiên, hãy cùng tìm hiểu về mức tối thiểu phải thực hiện để giao tiếp với Arduino thông qua chuỗi nối tiếp. Sẽ nhanh chóng rõ ràng rằng mã này không phù hợp với lối chơi nhưng tốt hơn là bạn nên xem qua từng bước và tìm hiểu những hạn chế là gì.

Trong Unity, tạo một cảnh mới với một GameObject trống duy nhất có tên là ArduinoReceive tại tệp đính kèm, một tập lệnh C # cũng có tên là ArduinoReceive. Tập lệnh này là nơi chúng tôi sẽ thêm tất cả mã xử lý giao tiếp với Arduino.

Có một thư viện phải được truy cập trước khi chúng tôi có thể giao tiếp với các cổng nối tiếp của máy tính của bạn; Unity phải được thiết lập để cho phép một số thư viện được sử dụng. Đi tới Edit-> ProjectSerring-> Player và bên cạnh Mức tương thích Api trong phần chuyển đổi Cấu hình. NET 2.0 Subset thành. NET 2.0. Bây giờ hãy thêm đoạn mã sau vào đầu tập lệnh;

sử dụng System. IO. Ports;

Điều này sẽ cho phép bạn truy cập lớp SerialPort mà bạn có thể định nghĩa như một đối tượng cho Lớp ArduinoReceive. Đặt nó ở chế độ riêng tư để tránh bất kỳ sự can thiệp nào từ tập lệnh khác.

SerialPort arduinoPort riêng;

Đối tượng arduinoPort có thể được mở bằng cách chọn đúng cổng (ví dụ: nơi Arduino được kết nối với USB) và tốc độ truyền (tức là tốc độ thông tin được gửi). Nếu bạn không chắc Arduino được cắm vào cổng nào, bạn có thể tìm hiểu trong trình quản lý thiết bị hoặc bằng cách mở Arduino IDE. Đối với tốc độ truyền, giá trị mặc định trên hầu hết các thiết bị là 9600, chỉ cần đảm bảo rằng bạn có giá trị này trong mã Arduino của mình và nó sẽ hoạt động.

Mã bây giờ sẽ trông như thế này;

sử dụng System. Collections;

sử dụng System. Collections. Generic; sử dụng UnityEngine; sử dụng System. IO. Ports; public class ArduinoReceive: MonoBehaviour {private SerialPort arduinoPort; // Sử dụng cái này để khởi tạo void Start () {arduinoPort = new SerialPort ("COM5", 9600); arduinoPort. Open (); WriteToArduino ("TRIGG"); }}

Số COM của bạn rất có thể sẽ khác. Nếu bạn đang sử dụng MAC, tên COM của bạn có thể có tên giống như sau /dev/cu.wchusbserial1420. Đảm bảo rằng mã từ phần 4 được tải lên Arduino và màn hình nối tiếp được đóng cho phần còn lại của phần này và mã này biên dịch mà không có vấn đề gì.

Bây giờ chúng ta hãy gửi một yêu cầu đến Arduino mọi khung và ghi kết quả vào cửa sổ bảng điều khiển. Thêm hàm WriteToArduino vào lớp ArduinoReceive. Dấu xuống dòng và dòng mới là cần thiết để mã Arduino phân tích cú pháp lệnh đến đúng cách.

private void WriteToArduino (string message)

{message = message + "\ r / n"; arduinoPort. Write (tin nhắn); arduinoPort. BaseStream. Flush (); }

Hàm này sau đó có thể được gọi trong vòng lặp Cập nhật.

void Update ()

{WriteToArduino ("TRIGG"); Debug. Log ("Giá trị đầu tiên:" + arduinoPort. ReadLine ()); Debug. Log ("Giá trị thứ hai:" + arduinoPort. ReadLine ()); }

Đoạn mã trên là mức tối thiểu bạn cần để đọc dữ liệu từ Arduino. Nếu bạn chú ý đến FPS được đưa ra bởi sự thống nhất, bạn sẽ thấy hiệu suất giảm đáng kể. Trong trường hợp của tôi, nó tăng từ khoảng 90 FPS mà không cần đọc / ghi lên 20 FPS. Nếu dự án của bạn không yêu cầu cập nhật thường xuyên thì có thể là đủ nhưng đối với trò chơi điện tử, 20 FPS là quá thấp. Phần tiếp theo sẽ trình bày cách bạn có thể cải thiện hiệu suất bằng cách sử dụng đa luồng.

Bước 4: Tối ưu hóa Truyền dữ liệu

Phần trước đã trình bày cách thiết lập cơ bản

giao tiếp giữa Arduino và chương trình Unity. Vấn đề chính với mã này là hiệu suất. Trong quá trình triển khai hiện tại, Unity phải đợi Arduino nhận, xử lý và trả lời yêu cầu. Trong thời gian đó, mã Unity phải đợi yêu cầu được thực hiện và không làm gì khác. Chúng tôi đã giải quyết vấn đề này bằng cách tạo một luồng sẽ xử lý các yêu cầu và lưu trữ biến trên luồng chính.

Để bắt đầu, chúng ta phải bao gồm thư viện phân luồng bằng cách thêm;

sử dụng System. Threading;

Tiếp theo, chúng tôi thiết lập chức năng mà chúng tôi đang bắt đầu trong các chủ đề. AsynchronousReadFromArduino bắt đầu bằng cách ghi yêu cầu tới Arduino bằng hàm WrtieToArduino. Việc đọc được bao bọc trong một khối try-catch, nếu hết thời gian đọc, các biến vẫn trống và hàm OnArduinoInfoFail được gọi thay vì OnArduinoInfoReceive.

Tiếp theo, chúng tôi xác định các chức năng OnArduinoInfoFail và OnArduinoInfoReceive. Đối với hướng dẫn này, chúng tôi in kết quả ra bảng điều khiển nhưng bạn có thể lưu trữ kết quả vào các biến bạn cần cho dự án của mình.

private void OnArduinoInfoFail ()

{Debug. Log ("Không đọc được"); } private void OnArduinoInfoReceive (xoay chuỗi, tốc độ chuỗi) {Debug. Log ("Readin Sucessfull"); Debug. Log ("Giá trị đầu tiên:" + vòng quay); Debug. Log ("Giá trị thứ hai:" + tốc độ); }

Bước cuối cùng là bắt đầu và dừng các luồng sẽ yêu cầu các giá trị từ Arduino. Chúng tôi phải đảm bảo rằng chuỗi cuối cùng được thực hiện xong với nhiệm vụ cuối cùng của nó trước khi bắt đầu một chuỗi mới. Nếu không, nhiều yêu cầu có thể được thực hiện với Arduino cùng một lúc, điều này có thể gây nhầm lẫn giữa Arduino / Unity và mang lại kết quả không thể đoán trước.

luồng riêng activeThread = null;

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

Nếu bạn so sánh hiệu suất của mã với mã mà chúng tôi đã viết ở phần 5, thì hiệu suất sẽ được cải thiện đáng kể.

private void OnArduinoInfoFail ()

{Debug. Log ("Không đọc được"); }

Bước 5: Tiếp theo ở đâu?

Nơi nào tiếp theo?
Nơi nào tiếp theo?

Chúng tôi đã chuẩn bị một bản demo mà bạn có thể tải xuống trên Github của chúng tôi (https://github.com/AlexandreDoucet/InfinityBike), tải xuống mã và trò chơi và đi qua đường đua của chúng tôi. Tất cả đều được thiết lập để tập luyện nhanh chóng và chúng tôi hy vọng nó có thể cung cấp cho bạn hương vị về những gì bạn có thể xây dựng nếu bạn sử dụng những gì chúng tôi đã dạy bạn với tài liệu hướng dẫn này.

Tín dụng

Những người đóng góp cho dự án

Alexandre Doucet (_Doucet_)

Maxime Boudreau (MxBoud)

Nguồn cung cấp bên ngoài [Công cụ trò chơi Unity] (https://unity3d.com)

Dự án này bắt đầu sau khi chúng tôi đọc hướng dẫn của Allan Zucconi "cách tích hợp Arduino với Unity" (https://www.alanzucconi.com/2015/10/07/how-to-int…)

Yêu cầu từ Arduino được xử lý bằng thư viện SerialCommand (https://github.com/kroimon/Arduino-SerialCommand)

Đề xuất: