基于 Arduino 与灯带的贪吃蛇游戏

大一定下的的创客项目以及 Dev Feast 活动上展示内容,主要技术栈在短学期已搞定,关键在于贪吃蛇游戏的实现。

项目地址:https://github.com/SRayJay/snake

一、项目简介

本项目开发一个运行在 ESP32 上,显示在 WS2812 灯板上并收 flutterAPP 控制的贪吃蛇游戏。

二、项目准备

所需的材料:ESP32 一个,22*22 的 WS2812 灯屏、电源、亚克力板

所做的准备:MQTT 服务器、arduino 开发环境、FastLED 库,flutter 开发框架。

三、灯带的使用

FastLED 灯带库

项目采用 FastLED 库来控制灯带。

首先引入头文件,并作一些定义。

#include<FastLED.h> //引入头文件
#define PIN 22 //esp32上连接灯带的引脚
#define MAXLED 484 //最大灯数
CRGB leds[MAXLED]; //定义了rgb灯珠数组

然后在 setup 函数内添加一些内容

void setup(){
 Serial.begin(115200);
 FastLED.setBrightness(64);
 FastLED.addLeds<WS2812,PIN,GRB>(leds,MAXLED);
}

灯带亮灭的用法

leds[0] = CRGB(255,0,0);
leds[1] = CRGB(0,255,0);
FastLED.show();
delay(80);

以上代码要求第一盏灯(数组从 0 开始)显示红色,第二盏灯显示绿色。前两句只是绑定颜色信息在灯珠上,真正产生效果的在 FastLED.show()这一句,delay(80)是 80 毫秒的延迟。

四、MQTT 服务器与 wifi 连接的准备

WIFI 的连接

#include<WiFi.h>//先导入库
const char* ssid = "xxxx";//WiFi的ssid和密码可以通过常量定义
const char* password = "xxxx";
void setup(){
    Serial.begin(115200);
 setup_wifi();//初始化函数调用启动wifi函数
}
void setup_wifi(){
    delay(10);
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid,password);
    while(WiFi.status()!=WL_CONNECTED){
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");//连接成功后显示IP信息
    Serial.println("IP address:");
    Serial.println(WiFi.localIP());
}

MQTT 服务器的连接

首先得准备一台可订阅发布消息的 mqtt 服务器,准备好后通过它的地址来连接。(这里省去了 mqtt 服务器用户信息,是不够安全的,可以自行设置用户名与密码信息。

#include<PubSubClient.h>
const char* mqtt_server = "xxxx";
WiFiClient espClient;
PubSubClient client(espClient);
void setup(){
    client.setServer(mqtt_server,1883);
    client.setCallback(callback);//设置回调函数,每次接收到消息都执行callback函数
}
void reconnect(){
    while(!client.connected()){
        Serial.print("Attempting MQTT connection...");
        if(client.connect("ESP32Client")){
            Serial.println("connected");
            client.subscribe("mode");//订阅一个名为mode的主题
            client.subscribe("control");//订阅control主题
        }else{
            Serial.print("failed,rc=");
            Serial.print(client.state());
            Serial.println(" try again in 5 seconds");
            delay(5000);
        }
    }
}
void loop() {
    if(!client.connected()){//客户端连接断开后执行重连
        reconnect();
    }
    client.loop();//连接成功后反复执行的循环函数,当它开始循环客户端才开始工作。
}

回调函数

每次回调执行的内容,传入的参数分别是主题、消息内容、长度。这里使用字符串比较函数 strcmp(str1,str2)来判断新传入的消息的主题是哪一个,这个函数返回的是一个整形数值,当它为 0 的时候表示两个字符串相同。

void callback(char* topic,byte* payload,unsigned int length){
  if(strcmp(topic,"mode")==0){
    opt = (char)payload[0];
    Serial.println(opt);
  }else if(strcmp(topic,"control")==0){
    towards = (char)payload[0];
    Serial.println(towards);
  }
}

五、贪吃蛇基本原理

1、地图的初始化

地图是 22*22 的矩形,可看成一个二维数组 maps[22][22],内容为该点的状态信息,在这个项目里,一个点可能有四种情况,我们设置 0 代表该点为空,1 代表该点是墙体,2 代表该点是食物,3 代表该点是蛇身。对于没有墙体的最简单的模式,可以在初始化函数里将所有点初始化为 0。

2、地图与灯珠信息的转换

地图是 22*22 的二维数组,而灯带是 484 个长度的一维数组,在改变了地图信息后,需要相应的对灯带信息作出改变。而 ws2812 矩形灯板走的是单总线,是不断反转迂回的,所以第 21 盏灯和第 22 盏灯在不同行但是位置是上下邻接的。奇数行时灯带位置从左到右递增,偶数行则是从右到左递增。可以根据这一特点来写一段转换代码,或者将其封装为函数,在每次需要做出改变时调用。

for(i=0;i<MAXLED;i++){
    if((i/22)%2==0){
       if(maps[i/22][i%22] == 1){
          leds[i] = CRGB(0,0,255);
       }
    }else{
      if(maps[i/22][21-i%22] == 1){
        leds[i] = CRGB(0,0,255);
      }
    }
  }
//这段代码遍历所有灯,当灯的位置在奇数行时是从左到右递增,偶数行反之,转换的结果将地图数组为1的点设置为墙体,亮蓝光。

3、食物的结构

食物的位置需要一个坐标信息,可以采用一维坐标也可以采用二维坐标,但二维坐标显然更简洁、明了,于是全局定义 food 结构,包含 xy 坐标。并写出食物生成函数。

struct Food{
  int x;
  int y;
}food;
void create_food(){
  int i,j;
  do{
    i = random(22);
    j = random(22);
  }while(maps[i][j]!=0);
  food.y = i;
  food.x = j;
  maps[i][j]=2;
  if(i%2==1){
    int pos;
    pos=(i+1)*22-1-j;
    leds[pos]=CRGB(0,255,0);
  }else{
    leds[i*22+j]=CRGB(0,255,0);
  }
}

这段代码实现了生成食物的随机性,直到随机出一个空的点来生成食物,在 maps 数组注册信息,并转换为一维坐标将该点对应的灯点亮为绿色。

4、蛇身结构

因为蛇身总是连着的,身体各部分相对位置是不变的 ,每次移动时观察头部和尾部,头部和尾部都会朝着当前的方向移动一格。用灯的视角来看,就是头部的前一方向在移动后亮起,而尾部则熄灭,这样的过程可以模拟蛇的移动。根据这些特点,我们采用链表的结构来定义蛇身。

typedef struct Snakes{
  int x;
  int y;
  struct Snakes *next;
}snake;
snake *head;
//游戏开始时执行create_snake来初始化蛇
//坐标参数可根据设定的障碍物来设置
void create_snake(){
  head = (snake*)malloc(sizeof(snake));
  head->x = 12;
  head->y = 10;
  snake *p = (snake*)malloc(sizeof(snake));
  snake *q = (snake*)malloc(sizeof(snake));
  p->x = 11;
  p->y = 10;
  q->x = 10;
  q->y = 10;
  head->next = p;
  p->next = q;
  q->next = NULL;
  //在地图数组上注册这些位置为蛇身
  maps[head->y][head->x]=3;
  maps[p->y][p->x]=3;
  maps[q->y][q->x]=3;
  //蛇身位置的灯将显示红色
  leds[(head->y)*22+head->x]=CRGB(255,0,0);
  leds[(p->y)*22+p->x]=CRGB(255,0,0);
  leds[(q->y)*22+q->y]=CRGB(255,0,0);
}

5、蛇的移动

移动的过程这里采用了尾部先消失,再出现新头部的方法。新头部的位置需要判断是否为食物,若为食物,头部由食物的绿色变为红色,尾部也需要重新点亮,表示增加一个长度。

char towards = 'D';
//towards是一个全部变量,表示蛇的移动方向,默认为'D',也可以在不同关卡修改默认值
void snake_moving(){
  int x = head->x, y = head->y;
  snake *p = head->next;
  //先默认关闭最后一盏灯
  while(1){
    if(p->next == NULL){
      turn_down(p->y,p->x);
      break;
    }
    p = p->next;
  }
  switch(towards){
    case 'W':
            if(head->y==0){
              head->y = 21;
            }else{
              head->y -= 1;
            }
            judge();
            //judge函数用于判断是否咬到墙或者蛇身来结束游戏
            Serial.println("向上一步走");
            break;
    case 'A':
            if(head->x == 0){
              head->x = 21;
            }else{
              head->x -= 1;
            }
            judge();
            Serial.println("向左一步走");
            break;
    case 'S':
            if(head->y == 21){
              head->y = 0;
            }else{
              head->y += 1;
            }
            judge();
            Serial.println("向下一步走");
            break;
    case 'D':
            if(head->x == 21){
              head->x = 0;
            }else{
              head->x += 1;
            }
            judge();
            Serial.println("向右一步走");
            break;
  }
  ChangeBody(y,x);
}
void ChangeBody(int y,int x){
  snake *p = head->next;
  int pos_a,pos_b,pos_x,pos_y;
  pos_a = x;
  pos_b = y;
  while(p!=NULL){
    pos_x = p->x;
    pos_y = p->y;
    p->x = pos_a;
    p->y = pos_b;
    pos_a = pos_x;
    pos_b = pos_y;
    p = p->next;
  }
  if(head->x == food.x&&head->y == food.y){
    snake *_new = (snake*)malloc(sizeof(snake));
    p = head;
    while(p->next!=NULL){
      p = p->next;
    }
    _new->x = pos_a;
    _new->y = pos_b;
    p->next = _new;
    _new ->next = NULL;
    turn_on_body(head->y,head->x);
    turn_on_body(_new->y,_new->x);
    //吃到食物时,蛇头由食物的绿变红,蛇尾需要重新点亮
    Serial.println("吃到食物");
    snake_len++;
    //吃到食物后再随机生成新的食物
    create_food();
  }else{
    turn_on_body(head->y,head->x);
    //没吃到食物,根据正常行进,前面已经turn_down蛇尾,这会儿点亮蛇头就行
  }
  //turn_on_body和turn_down两个函数只改变点的颜色与坐标信息,还需要外面调用show函数来刷新
  FastLED.show();
}
void turn_on_body(int y,int x){
  int pos;
  pos = (y + 1) * 22 - 1 - x;
  if(y % 2 ==1){
    leds[pos] = CRGB(255,0,0);
  }else{
    leds[y * 22 + x] = CRGB(255,0,0);
  }
  maps[y][x] = 3;
}
void turn_down(int y,int x){
  int pos;
  pos = (y + 1) * 22 - 1 - x;
  if(y%2==1){
    leds[pos] = CRGB(0,0,0);
  }else{
    leds[y*22+x] = CRGB(0,0,0);
  }
  maps[y][x] = 0;
}

6、模式的选择

程序刚启动时是一条小蛇不断地移动作为等待界面,这个时候需要你给出一个模式的指令来开始游戏。

void welcome(){
  leds[0] = CRGB(255,0,0);
  leds[1] = CRGB(255,0,0);
  for(int i=2;i<483;i++){
    client.loop();
    //这句表示它时刻接收传入数据,若opt改变,就终止等待界面,开始游戏
    if(opt!='0'){
      break;
    }
    leds[i] = CRGB(255,0,0);
    leds[i+1] = CRGB(255,0,0);
    leds[i-2] = CRGB(0,0,0);
    FastLED.show();
    delay(80);
  }
}
char opt='0';//模式值默认为'0',等待界面
void loop() {
  if(!client.connected()){
    reconnect();
  }
  client.loop();
  if(opt == '0'){
    welcome();
  }else if(opt == '2'){
    mode2();
    //四面都是墙
  }else if(opt == '1'){
    infinity_mode();
    //无墙
  }else if(opt == '3'){
    mode3();
    //四个L字墙
  }else if(opt == '4'){
    mode4();
  }else if(opt == '5'){
    mode5();
  }else if(opt == '6'){
    mode6();
  }else if(opt == '7'){
    mode7();
  }else if(opt == '8'){
    mode8();
  }else if(opt == '9'){
    mode9();
  }else if(opt == 'B'){
    battle_mode();
    //闯关模式,吃十五个食物进入下一关
  }
}

上面代码涉及到的其他的 mode,都可以自行设计,下面给出一个例子。

void mode5(){
  clear_all();//清空地图的信息
  create_wall5();//初始化关卡5的墙体
  create_food();
  create_snake();
  FastLED.show();
  delay(1000);
  client.publish("snake_len","3");
  //这里发布了主题为snake_len的消息,将蛇身长度传到app,只需要app接收这个主题的消息即可。
  while(opt == '5'){
    client.loop();
    check_mode('5');//该函数检查有没有改变模式
    snake_moving();
    delay_time();//根据蛇身长度区间来加快蛇移动速度的函数
  }
}
void clear_all(){
  for(int i = 0;i<484;i++){
    leds[i] = CRGB(0,0,0);
  }
  for(int i = 0;i<22;i++){
    for(int j = 0;j<22;j++){
      maps[i][j] = 0;
    }
  }
  snake_len = 3;
  score = 0;
  itoa(score,score_msg,10);
  client.publish("score",score_msg);
  //这里发布的score主题的消息,表示分数,传给app
}
void create_wall5(){
  int i,j;
  for(j=0;j<10;j++){
    maps[9][j] = 1;
  }
  for(i=0;i<9;i++){
    maps[i][9] = 1;
  }
  for(j=16;j<22;j++){
    maps[5][j] = 1;
    maps[13][j] =1;
  }
  for(i=6;i<13;i++){
    maps[i][16] = 1;
  }
  for(i=16;i<22;i++){
    maps[i][6] = 1;
    maps[i][17] = 1;
  }
  for(j=6;j<18;j++){
    maps[16][j] = 1;
  }
  for(i=0;i<MAXLED;i++){
    if((i/22)%2==0){
       if(maps[i/22][i%22] == 1){
          leds[i] = CRGB(0,0,255);
       }
    }else{
      if(maps[i/22][21-i%22] == 1){
        leds[i] = CRGB(0,0,255);
      }
    }
  }
}
void check_mode(char now_mode){
  if(opt!=now_mode){
    if(opt == '0'){
      welcome();
    }else if(opt == '2'){
      mode2();
      //四面都是墙
    }else if(opt == '1'){
      infinity_mode();
      //无墙
    }else if(opt == '3'){
      mode3();
      //四个L字墙
    }else if(opt == '4'){
      mode4();
    }else if(opt == '5'){
      mode5();
    }else if(opt == '6'){
      mode6();
    }else if(opt == '7'){
      mode7();
    }else if(opt == '8'){
      mode8();
    }else if(opt == '9'){
      mode9();
    }else if(opt == 'B'){
      battle_mode();
      //闯关模式,吃十五个食物进入下一关
    }
  }
}
void delay_time(){
  if(snake_len<=10){
      delay(475);
    }else if(snake_len<=15){
      delay(375);
    }else{
      delay(275);
   }
}

贪吃蛇的核心代码到这里就差不多了,剩下的都是自行设计,详细代码可参考项目。

六、Flutter 控制器

1. mqtt 依赖

首先新建一个项目,在 pubspec.yaml 文件 dependencies 中加入 mqtt 应用依赖

mqtt_client: ^5.5.3

然后点击右上角 Package get 获取相关的依赖.

完成之后在 lib 目录下新建一个 package,并且新建一个 dart 文件 message.dart,在里面黏上下面的代码:

import 'package:mqtt_client/mqtt_client.dart' as mqtt;

class Message {
 final String topic;
 final String message;
 final mqtt.MqttQos qos;

 Message({this.topic, this.message, this.qos});
}

这个是一个 mqtt 消息的类,里面有一个消息的主题、内容和 Qos。

2、mqtt 相关代码

  void _connect() async{
    //client连接的初始化配置
    //默认端口1883,如果不是1883就采用
    //client = mqtt.MqttClient.withPort(broker, '',1883);
    client = mqtt.MqttClient(broker,'');
    client.logging(on: true);

    client.keepAlivePeriod = 30;

    client.onDisconnected = _onDisconnected;

    final mqtt.MqttConnectMessage connMess = mqtt.MqttConnectMessage()
        .withClientIdentifier('*******')//连接的id
        .startClean()
        .keepAliveFor(30)
        .withWillTopic('willtopic')
        .withWillMessage('My Will message')
        .withWillQos(mqtt.MqttQos.atLeastOnce);
    print('MQTT client connecting....');
    client.connectionMessage = connMess;
    try {
      await client.connect();
    } catch (e) {
      print(e);
      _disconnect();
    }

    if (client.connectionState == mqtt.MqttConnectionState.connected) {
      print('MQTT client connected');
      setState(() {
        connectionState = client.connectionState;
      });

    } else {
      print('ERROR: MQTT client connection failed - '
          'disconnecting, state is ${client.connectionState}');

      _disconnect();
    }

    subscription = client.updates.listen(_onMessage);

  }
  void _disconnect() {
    client.disconnect();
    _onDisconnected();
  }

  void _onDisconnected() {
    setState(() {
      connectionState = client.connectionState;
      client = null;
    });
    print('MQTT client disconnected');
  }
  void _onMessage(List<mqtt.MqttReceivedMessage> event) {
    print(event.length);
    print(event[0].topic);
    final mqtt.MqttPublishMessage recMess = event[0].payload as mqtt.MqttPublishMessage;
    final String message = mqtt.MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
    print('MQTT message: topic is <${event[0].topic}>, '
        'payload is <-- ${message} -->');
    print(client.connectionState);
    setState(() {
      //在这个地方判断接收的消息的主题并接收消息
      if(event[0].topic=='snake_len'){
        snake_len = int.parse(message);
        print(snake_len);
      }else if(event[0].topic=='score'){
        score = int.parse(message);
        print(score);
      }
    });
  }
  void _subMessage(){
    //开始接收subtopic的submessage
    print("on sub message");
    if(connectionState == mqtt.MqttConnectionState.connected){
      setState(() {
        print('subscribe to ${_subSnakeLen}');
        client.subscribe(_subSnakeLen, mqtt.MqttQos.exactlyOnce);
        client.subscribe(_subScore, mqtt.MqttQos.exactlyOnce);
      });
    }
  }
  void _pubMessage(){
    //发布控制消息
    final mqtt.MqttClientPayloadBuilder builder =
    mqtt.MqttClientPayloadBuilder();
    builder.addString(_pubMsg);
    print("pub message ${_pubControl}:${_pubMsg}");
    client.publishMessage(
      _pubControl,
      mqtt.MqttQos.values[0],
      builder.payload,
      retain: _retainValue,
    );
  }
  void _pubMode(){
    //发布控制消息
    final mqtt.MqttClientPayloadBuilder builder =
    mqtt.MqttClientPayloadBuilder();
    builder.addString(_pubMsg);
    print("pub message ${_pubModeTopic}:${_pubMsg}");
    client.publishMessage(
      _pubModeTopic,
      mqtt.MqttQos.values[0],
      builder.payload,
      retain: _retainValue,
    );
  }

3、控制器界面

由于 app 控制器比较简洁,只有一个界面,不需要路由导航也不需要菜单栏,

import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_client.dart' as mqtt;
import 'model/message.dart';
import 'dart:async';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'snake',
      home: MyHomePage(title: 'snake'),
    );

  }
}

class MyHomePage extends StatefulWidget{
  MyHomePage({Key key,this.title}) : super(key:key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>{
  int snake_len = 3;
  int score = 0;
  String _pubControl = 'control';
  String _pubModeTopic = 'mode';
  String _subSnakeLen = 'snake_len';
  String _subScore = 'score';
  String _pubMsg;
  bool _retainValue = false;
  ScrollController subMsgScrollController = new ScrollController();
  String broker = '*****';//mqtt服务器地址
  mqtt.MqttClient client;
  mqtt.MqttConnectionState connectionState;
  StreamSubscription subscription;
  List<Message> messages = <Message>[];
  @override
  void initState(){
    super.initState();
    _connect();
    _subMessage();
  }
  //_FreeModeOption是一个弹窗界面,在按下自由模式时就会显示这个弹窗来选择
  //3*3的九个按钮,分别对应一种模式,在按下时绑定的消息就会发出,传达给esp32
  _FreeModeOption(){
    showDialog(
        context: context,
        builder: (BuildContext context){
          return new SimpleDialog(
                title: new Text('自由模式'),
                children: <Widget>[
                new Row(
                  children: <Widget>[
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '1';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式一'),
                      ),
                      margin: EdgeInsets.fromLTRB(10,0,0,0),
                    ),
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '2';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式二'),
                      ),
                      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
                    ),
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '3';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式三'),
                      ),
                      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
                    ),
                  ],
                ),
                new Row(
                  children: <Widget>[
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '4';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式四'),
                      ),
                      margin: EdgeInsets.fromLTRB(10,0,0,0),
                    ),
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '5';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式五'),
                      ),
                      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
                    ),
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '6';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式六'),
                      ),
                      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
                    ),
                  ],
                ),
                new Row(
                  children: <Widget>[
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '7';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式七'),
                      ),
                      margin: EdgeInsets.fromLTRB(10,0,0,0),
                    ),
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '8';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式八'),
                      ),
                      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
                    ),
                    new Container(
                      child: new OutlineButton(
                        onPressed: (){
                          _pubMsg = '9';
                          _pubMode();
                          score = 0;
                          _subMessage();
                        },
                        child: Text('模式九'),
                      ),
                      margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
                    ),
                  ],
                )],
              );
        }
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: new AppBar(title: Text(widget.title),),
      body: new Column(
        children: <Widget>[
          new Row(
            children: <Widget>[
              new Container(
                width:107,
                padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
                child:Text(
                    "蛇长:"+
                        snake_len.toString()
                ),
              ),
              new Container(
                padding: EdgeInsets.fromLTRB(180, 0, 0, 0),
                child: RaisedButton(
                  child: Text('冒险模式'),
                  onPressed: (){
                    _pubMsg = 'B';
                    _pubMode();
                    score = 0;
                    _subMessage();
                  },
                  textColor: Colors.white,
                  color: Colors.deepOrangeAccent,
                ),
              ),
            ],
          ),
          new Row(
            children: <Widget>[
              new Container(
                width: 107,
                padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
                child: Text(
                    "得分:"+
                        score.toString()
                ),
              ),
              new Container(
                margin: EdgeInsets.fromLTRB(180, 0, 0, 0),
                child: RaisedButton(
                  child: new Text('自由模式'),
                  onPressed: _FreeModeOption,
                  textColor: Colors.white,
                  color: Colors.deepOrangeAccent,
                ),
              )
            ],
          ),
          //方向控制分为三行
          new Row(
            children: <Widget>[
              new Container(
                padding: EdgeInsets.fromLTRB(175, 200, 0, 10),
                child: FloatingActionButton(
                  child: new Icon(Icons.arrow_drop_up),
                  onPressed: (){
                    _pubMsg = 'W';
                    //按下时传达的数据是'W',即把方向值传给esp32
                    _pubMessage();
                  },
                  backgroundColor: Colors.orangeAccent,
                ),
              ),
            ],
          ),
          new Row(
            children: <Widget>[
              new Container(
                padding: EdgeInsets.fromLTRB(110, 0, 30, 10),
                child: FloatingActionButton(
                    child:  new Icon(Icons.arrow_left),
                    onPressed:(){
                      _pubMsg = 'A';
                      _pubMessage();
                    },
                  backgroundColor: Colors.orangeAccent,
                ),
              ),
              new Container(
                padding: EdgeInsets.fromLTRB(45, 0, 0, 10),
                child: FloatingActionButton(
                  onPressed: (){
                    _pubMsg = 'D';
                    _pubMessage();
                  },
                  child: new Icon(Icons.arrow_right),
                  backgroundColor: Colors.orangeAccent,
                ),
              ),
            ],
          ),
          new Row(
            children: <Widget>[
              new Container(
                padding: EdgeInsets.fromLTRB(175, 0, 0, 0),
                child: FloatingActionButton(
                    onPressed:(){
                      _pubMsg = 'S';
                      _pubMessage();
                    },
                    child: new Icon(Icons.arrow_drop_down),
                    backgroundColor: Colors.orangeAccent,
                ),
              )
            ],
          )
        ],
      ),
    );
  }

到此为止,代码部分基本完毕,连接好 esp32 与 ws2812 灯带,给 esp32 供电和联网,再启动 app 就可以开始游戏了。游戏的更多内容可以自行设计。完整代码见项目地址。

posted @ 2023-06-27 14:30  脏猫  阅读(391)  评论(0)    收藏  举报