Arduino Racing Game - Гоночная игра на Arduino (победитель конкурса)
1. Цель проекта
Создать интересную и уникальную игру для развлечения, повысив свои навыки.
Игрок управляет машиной, на машину движутся препятствия, которые игроку необходимо объезжать. Есть несколько разных уровней сложности и возможность посмотреть рекорды. Видео геймплея:
3. Необходимые компоненты
1. Arduino Nano из GyverKIT.
2. Экран 16х2 с i2c преобразователем из GyverKIT.
3. Джойстик из GyverKIT.
4. Батарейный отсек из GyverKIT.
5. 8 соединительных проводов из GyverKIT.
6. Штатив для телефона (100-200 руб с Aliexpress).
7. Антистатический пакет.
8. 2 винта/самореза.
9. Изолента.
10. Термоклей.
4. Процесс сборки
1. Джойстик.
Берём штатив, и присоединяем к нему джойстик с помощью двух винтов или саморезов. Чтобы было проще, можно шуруповертом заранее сделать отверстия в пластике.
2. Экран.
Разворачиваем прищепку штатива чтобы задняя часть смотрела на нас. Экран сюда походит по размерам как влитой, разве что может потребоваться немного подрезать пластик чтобы влезли контакты i2c модуля. Резать пластик удобно гравером, но можно использовать и пилу, паяльник, дрель или шуруповерт. Приклеиваем экран с обратной стороны на термоклей, чтобы не отвалился.
3. Батарейный отсек.
Лучше использовать батарейный отсек со встроенным переключателем. Если его нет – ничего страшного, можно будет выключать игру доставанием батарейки. Также лучше использовать отсек для батареек типа ААА, а не типа АА, так как они легче, но и менять их нужно чаще. Еще можно приклеить батарейный отсек к корпусу штатива, но я не увидел в этом необходимости.
В магазине не было отсека на 4 батарейки, поэтому мне пришлось купить два отсека на 2 батарейки АА и соединить их. Я приклеил их универсальным клеем к куску фанеры нужного размера и припаял плюс одного отсека к минусу другого. Теперь они как один отсек.
Также я сделал два выреза в фанере для переключателей. У штатива сверху была резиновая “пимпочка”, потянув за которую можно растягивать прищепку. Нам прищепку растягивать не требуется, поэтому я нашёл ей другое применение – приклеил её к одному из переключателей, чтобы было легче включать и выключать игру. Еще я сделал отверстие в фанере и батарейном отсеке чтобы можно было регулировать яркость экрана потенциометром, не снимая при этом батарейный отсек.
4. Arduino
Берем Arduino и припаиваем провода к ней согласно схеме. Прошиваем программу в Arduino.
5. Финиш
Если всё работает корректно, то можем заканчивать сборку и наслаждаться результатом. Заворачиваем Arduino в отрезанный под её размер кусок антистатического пакета и заматываем изолентой.
Помещаем её между ножками штатива и заматываем изолентой ножки чтобы они не разъезжались в стороны. Ножки бывают разных цветов, и лучше использовать изоленту того же цвета. Игра готова.
Вы можете скачать готовый скетч или разобраться подробнее в том, как работает программа.
Импорт библиотек и инициализация глобальных переменных
1: #include <EEPROM.h> // Библиотека для работы с памятью
2: #include <Wire.h> // Библиотека для работы с i2c
3: #include <LiquidCrystal_I2C.h> // Библиотека для работы с дисплеем
4: LiquidCrystal_I2C lcd(0x27, 16, 2); // Инициализация дисплея
5: uint8_t b[8]={0x1f, 0x19, 0x15, 0x13, 0x19, 0x15, 0x13, 0x1f}; // Графическая модель препятствия
6: uint8_t Coords[16]; // В этом массиве хранится информация о местонахождении препятствий, 0 – нет препятствия, 1 – есть слева, 2 – есть справа
7: int last=0; // Последнее положение препятствия, переменная нужна чтобы предотвратить спавн препятствия сразу после следующего чтобы дать игроку возможность проехать между ними
8: int last2=0; // Эта переменная означает количество свободного места
9: unsigned long int points=0; // Счетчик очков игрока
10: int i,j,c; // В переменной с храниться информация о том где заспавнится препятствие, 0 – нигде, 1 – слева, 2 -
11: uint8_t pos=0; // Текущая позиция машины, 0 – слева, 1 - справа
12: uint8_t car1[8]={0x18,0x1e,0x5,0x19,0x19,0x5,0x1e,0x18}; // Графическая модель передней части
13: uint8_t car2[8]={0xc,0xf,0x14,0x13,0x13,0x14,0xf,0xc}; // Графическая модель задней части машины
14: unsigned long int e; // Последнее сохраненное значение переменной millis()
15: int velocity, sectors; // Эти параметры зависят от сложности, velocity – скорость, sectors – минимальное расстояние между двумя препятствиями
16: int dif; // Сложность
Функция, обновляющая положение машины на дисплее
17: void printcar(){
18: if (pos==0){
19: lcd.setCursor(1,0);
20: lcd.write(1);
21: lcd.setCursor(0,0);
22: lcd.write(2);
23: lcd.setCursor(0,1);
24: lcd.print(" ");
25: lcd.setCursor(1,1);
26: lcd.print(" ");
27: }
28: else{
29: lcd.setCursor(1,1);
30: lcd.write(1);
31: lcd.setCursor(0,1);
32: lcd.write(2);
33: lcd.setCursor(0,0);
34: lcd.print(" ");
35: lcd.setCursor(1,0);
36: lcd.print(" ");
37: }
38: }
Функция, срабатывающая при изменении положения джойстика. Она проверяет есть ли препятствие на полосе, на которую игрок хочет перестроится. Если есть, то возвращает 0 и игра окончена. Если нет, вызывает функцию printcar. Функция, перемещающая все объекты на 1 клетку ближе к игроку
39: int turn(){
40: if (analogRead(A1)>640){
41: if (pos==0){
42: if (pos==0){
43: if ((Coords[0]==2)||(Coords[1]==2)){
44: return 0;
45: }
46: }
47: else{
48: if ((Coords[0]==1)||(Coords[1]==1)){
49: return 0;
50: }
51: }
52: pos=1;
53: printcar();
54: }
55: }
56: else if(analogRead(A1)<384){
57: if (pos==1){
58: if (pos==0){
59: if ((Coords[0]==2)||(Coords[1]==2)){
60: return 0;
61: }
62: }
63: else{
64: if ((Coords[0]==1)||(Coords[1]==1)){
65: return 0;
66: }
67: }
68: pos=0;
69: printcar();
70: }
71: }
72: return 1;
73: }
Эта функция вызывается каждый такт и перемещает все препятствия на 1 клетку
74:
75: void moving(){
76: for (i=0;i<3;i++){
77: if (pos==0){
78: if (Coords[i]!=Coords[i+1]){
79: Coords[i]=Coords[i+1];
80: if (Coords[i]==1){
81: lcd.setCursor(i,1);
82: lcd.print(" ");
83: }
84: else if (Coords[i]==2){
85: lcd.setCursor(i,1);
86: lcd.write(0);
87: }
88: else{
89: lcd.setCursor(i,1);
90: lcd.print(" ");
91: }
92: }
93:
94: }
95: else{
96: if (Coords[i]!=Coords[i+1]){
97: Coords[i]=Coords[i+1];
98: if (Coords[i]==1){
99: lcd.setCursor(i,0);
100: lcd.write(0);
101: }
102: else if (Coords[i]==2){
103: lcd.setCursor(i,0);
104: lcd.print(" ");
105: }
106: else{
107: lcd.setCursor(i,0);
108: lcd.print(" ");
109: }
110: }
111: }
112: }
113: for (i=2;i<15;i++){
114: if (Coords[i]!=Coords[i+1]){
115: Coords[i]=Coords[i+1];
116: if (Coords[i]==1){
117: lcd.setCursor(i,0);
118: lcd.write(0);
119: lcd.setCursor(i,1);
120: lcd.print(" ");
121: }
122: else if (Coords[i]==2){
123: lcd.setCursor(i,1);
124: lcd.write(0);
125: lcd.setCursor(i,0);
126: lcd.print(" ");
127: }
128: else{
129: lcd.setCursor(i,0);
130: lcd.print(" ");
131: lcd.setCursor(i,1);
132: lcd.print(" ");
133: }
134: }
135: }
136: }
Эта функция перемещает все препятствия на 1 без спавна новых препятствий. Она вызывается несколько раз после спавна препятствия, чтобы сделать свободное пространство между препятствиями.
137: int movingwithoutrand(){
138: next barrier
139: for (j=0;j<sectors;j++){
140: if (pos+1==Coords[2]){
141: return 0;
142: }
143: moving();
144: Coords[15]=0;
145: lcd.setCursor(15,0);
146: lcd.print(" ");
147: lcd.setCursor(15,1);
148: lcd.print(" ");
149: if (turn()==0){
150: return 0;
151: }
152: points++;
153: e=millis();
154: while(millis()-e<velocity){
155: if (turn()==0){
156: return 0;
157: }
158: }
159:
160: }
161: return 1;
162: }
Функция изменения уровня сложности в меню показа максимальных набранных очков
163: void dispscore(){
164: byte val1 = EEPROM.read((dif-1)*4+1);
165: byte val2 = EEPROM.read((dif-1)*4+2);
166: byte val3 = EEPROM.read((dif-1)*4+3);
167: byte val4 = EEPROM.read((dif-1)*4+4);
168: unsigned long int val = val1*pow(2,24)+val2*pow(2,16)+val3*(pow(2,8))+val4;
169: switch (dif){
170: case 1:
171: lcd.clear();
172: lcd.setCursor(0,0);
173: lcd.print(" TripMode > ");
174: break;
175: case 2:
176: lcd.clear();
177: lcd.setCursor(0,0);
178: lcd.print(" < Easy > ");
179: break;
180: case 3:
181: lcd.clear();
182: lcd.setCursor(0,0);
183: lcd.print(" < Normal > ");
184: break;
185: case 4:
186: lcd.clear();
187: lcd.setCursor(0,0);
188: lcd.print(" < Hard > ");
189: break;
190: case 5:
191: lcd.clear();
192: lcd.setCursor(0,0);
193: lcd.print("< ImpossibleFast");
194: break;
195: }
Функция интерфейса меню просмотра максимальных очков.
1: void maxscores(){ // "difficulty" procedure calls it when user picks up "Max Score" option on the menu.
2: dif=3;
3: lcd.clear();
4: lcd.setCursor(3,0);
5: lcd.print("< Normal >");
6: lcd.setCursor(0,1);
7: byte val1 = EEPROM.read((dif-1)*4+1);
8: byte val2 = EEPROM.read((dif-1)*4+2);
9: byte val3 = EEPROM.read((dif-1)*4+3);
10: byte val4 = EEPROM.read((dif-1)*4+4);
11: unsigned long int val = val1*pow(2,24)+val2*pow(2,16)+val3*(pow(2,8))+val4;
12: lcd.print(val);
13: while (1){
14: if ((analogRead(A1)>640) && (dif<5)){
15: dif++;
16: dispscore();
17: while(analogRead(A1)>640){
18: delay(5);
19: }
20: }
21: else if ((analogRead(A1)<384) && (dif>1)){
22: dif--;
23: dispscore();
24: while(analogRead(A1)<384){
25: delay(5);
26: }
27: }
28: else if (digitalRead(3)==0){
29: while(digitalRead(3)==0){
30: delay(5);
31: }
32: break;
33: }
34:
35: }
36: difficulty();
37: }
Функция меняет название уровня сложности на экране и устанавливает параметры для выбранного уровня.
38: void dispdif(){
39: switch (dif){
40: case 0:
41: lcd.setCursor(0,1);
42: lcd.print(" Max Scores > ");
43: break;
44: case 1:
45: lcd.setCursor(0,1);
46: lcd.print(" < TripMode > ");
47: velocity=200;
48: sectors=3;
49: break;
50: case 2:
51: lcd.setCursor(0,1);
52: lcd.print(" < Easy > ");
53: velocity=150;
54: sectors=3;
55: break;
56: case 3:
57: lcd.setCursor(0,1);
58: lcd.print(" < Normal > ");
59: velocity=100;
60: sectors=3;
61: break;
62: case 4:
63: lcd.setCursor(0,1);
64: lcd.print(" < Hard > ");
65: velocity=150;
66: sectors=2;
67: break;
68: case 5:
69: lcd.setCursor(0,1);
70: lcd.print("< ImpossibleFast");
71: velocity=100;
72: sectors=2;
73: break;
74: }
75: }
Функция интерфейса меню выбора сложности.
76: void difficulty(){ // It is called before race is started to let user set the difficulty
77: dif=3;
78: velocity=100;
79: sectors=3;
80: lcd.clear();
81: lcd.setCursor(0,0);
82: lcd.print("Difficulty");
83: lcd.setCursor(3,1);
84: lcd.print("< Normal >");
85: while (1){
86: if ((analogRead(A1)>640) && (dif<5)){
87: dif++;
88: dispdif();
89: while(analogRead(A1)>640){
90: delay(5);
91: }
92: }
93: else if ((analogRead(A1)<384) && (dif>0)){
94: dif--;
95: dispdif();
96: while(analogRead(A1)<384){
97: delay(5);
98: }
99: }
100: else if (digitalRead(3)==0){
101: while(digitalRead(3)==0){
102: delay(5);
103: }
104: if (dif>0){
105: break;
106: }
107: else{
108: maxscores();
109: break;
110: }
111: }
112:
113: }
114: }
Функция setup.
115: void setup() {
116: lcd.begin();
117: lcd.backlight();
118: Serial.begin(9600);
119: lcd.createChar(0, b);
120: lcd.createChar(1,car1);
121: lcd.createChar(2,car2);
122: //lcd.createChar(1, c);
123:
124: lcd.setCursor(0,0);
125: lcd.print("Racing game");
126: lcd.setCursor(0,1);
127: lcd.print("Press the button");
128: pinMode(3,INPUT_PULLUP);
129: }
Функция loop. Она выполняется по кругу и управляет всем процессом игры.
130: void loop() {
131: if (digitalRead(3)==0){
132: while (1){
133: if (digitalRead(3)==1) break;
134: delay(5);
135: }
136: points=0;
137: difficulty();
138: randomSeed(millis() % 1000);
139: lcd.clear();
140: for (i=0;i<15;i++){
141: Coords[i]=0;
142: }
143: printcar();
144: while(1){
145:
146: c=random(3);
147: if (c!=0 & c!=last & last!=0 & last2<4){
148: if (movingwithoutrand()==0){
149: break;
150: }
151: }
152: if (pos+1==Coords[2]){
153: break;
154: }
155: moving();
156: Coords[15]=c;
157: if (Coords[15]==1){
158: lcd.setCursor(15,0);
159: lcd.write(0);
160: lcd.setCursor(15,1);
161: lcd.print(" ");
162: }
163: else if (Coords[15]==2){
164: lcd.setCursor(15,1);
165: lcd.write(0);
166: lcd.setCursor(15,0);
167: lcd.print(" ");
168: }
169: else{
170: lcd.setCursor(15,0);
171: lcd.print(" ");
172: lcd.setCursor(15,1);
173: lcd.print(" ");
174: }
175: if (Coords[15]!=0){
176: last=Coords[15];
177: last2=0;
178: }
179: else{
180: last2++;
181: }
182: if (turn()==0){
183: break;
184: }
185: points++;
186: e=millis();
187: while(millis()-e<velocity){
188: if (turn()==0){
189: break;
190: }
191: }
192: }
1: lcd.clear();
2: lcd.setCursor(0,0);
3: lcd.print("Game over");
4: if (points<10000){
5: lcd.setCursor(0,1);
6: lcd.print("Your points:");
7: lcd.setCursor(12,1);
8: lcd.print(points);
9: }
10: else if (points<1000000){
11: lcd.setCursor(0,1);
12: lcd.print("Ur points:");
13: lcd.setCursor(10,1);
14: lcd.print(points);
15: }
16: else if (points<1000000000){
17: lcd.setCursor(0,1);
18: lcd.print("Points:");
19: lcd.setCursor(7,1);
20: lcd.print(points);
21: }
22: else{
23: lcd.setCursor(0,1);
24: lcd.print("Find a girlfriend");
25: }
26: byte val1 = EEPROM.read((dif-1)*4+1);
27: byte val2 = EEPROM.read((dif-1)*4+2);
28: byte val3 = EEPROM.read((dif-1)*4+3);
29: byte val4 = EEPROM.read((dif-1)*4+4);
30: unsigned long int power = 2;
31: unsigned long int val = val1*pow(2,24)+val2*pow(2,16)+val3*(pow(2,8))+val4;
32: if (points>val){
33: if (points>=1000000000){
34: points=999999999;
35: }
36: val1=0;
37: val2=0;
38: val3=0;
39: val4=0;
40: val4=val4+bitRead(points, 0);
41: for (i=1;i<8;i++){
42: val4=val4+power*bitRead(points, i);
43: power*=2;
44: }
45: val3=val3+bitRead(points, 8);
46: power=2;
47: for (i=9;i<16;i++){
48: val3=val3+power*bitRead(points, i);
49: power*=2;
50: }
51: power=2;
52: val2=val2+bitRead(points, 16);
53: for (i=17;i<24;i++){
54: val2=val2+power*bitRead(points, i);
55: power*=2;
56: }
57: power=2;
58: val1=val1+bitRead(points, 24);
59: for (i=25;i<29;i++){
60: val1=val1+power*bitRead(points, i);
61: power*=2;
62: }
63: EEPROM.write((dif-1)*4+1, val1);
64: EEPROM.write((dif-1)*4+2, val2);
65: EEPROM.write((dif-1)*4+3, val3);
66: EEPROM.write((dif-1)*4+4, val4);
67: }
68: while (1){
69: if (digitalRead(3)==0){
70: while(digitalRead(3)==0){
71: delay(5);
72: }
73: break;
74: }
75: }
76: lcd.setCursor(0,0);
77: lcd.print("Racing game");
78: lcd.setCursor(0,1);
79: lcd.print("Press the button");
80:
81: }
82: }
6. Итак
Вы получили опыт взаимодействия со средой Arduino и интересную, уникальную, самодельную игру. Если что-то не работает – проверьте работоспособность каждого модуля по отдельности и качество пайки.
Автор проекта:
Кузнецов М.Р.
mikesprogramms@gmail.com
GitHub - @Mike-Kuznetsov
Данный проект победил в конкурсе Arduino 2022. Оценить работу Автора и увидеть другие его самоделки можно на его канале в Youtube: Заметки ESPшника
Товары
- Комментарии