Flutter开发之对角棋游戏实现实例详解

目录
  • 前沿
  • 演示效果
    • 对角棋规则
  • 实现思路
  • 具体实现
    • 1. 绘制棋盘
    • 2. 绘制棋子
    • 3. 手势处理
    • 4. 游戏规则
  • 优化
  • 总结

前沿

关于对角棋相信大家都不陌生,其凭借着规则简单又灵活多变成为我们童年不可缺少的益智游戏。

今天我将用Flutter来实现一个对角棋游戏,即巩固自己Flutter的绘制和手势知识,也希望这篇文章对大家有所帮助。

演示效果

老规矩,我们先演示下实现的最终效果:

对角棋规则

首先我们还是回顾下对角棋游戏的规则,这里借用 百度百科 的规则说明:

棋盘:象棋棋盘中,将士所在的带对角线的田字框。

棋子:双方各持三子,颜色不同。

初始:如图1所示,各自对立。

胜利条件:其中一方三子,占据一条对角线,或者对方没有棋子可以移动。

玩法:沿着棋盘划线,双方交互移动棋子,一次一只能移动一步,不包括交叉。

实现思路

  • 棋盘。绘制棋盘
  • 棋子。绘制棋子
  • 手势。处理点击棋子及移动位置手势
  • 规则。规则分为棋子移动规则、游戏胜利规则两部分

具体实现

1. 绘制棋盘

说到绘制,我们需要先创建 CustomPaint 通过自定义 CustomPainter 来实现。

CustomPaint(
    size: Size(width, height),
    painter: DiagonalChessPainter(),
)

考虑到我们要适配不同的手机尺寸,因此我们先通过 LayoutBuilder 测量整个 Widget 的尺寸,并计算棋盘在屏幕上位置。

LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          initPosition(constraints);
          return GestureDetector(
            onTapDown: _onTapDown,
            child: CustomPaint(
              size: Size(width, height),
              painter: DiagonalChessPainter(
                rWidth: rWidth,
                rHeight: rHeight,
                boardOffsetList: boardOffsetList,
              ),
            ),
          );
        },
      )

我们这里定义棋盘的九个点,从屏幕左上角开始,代码如下:

    width = constraints.maxWidth;
    height = constraints.maxHeight;
    rWidth = width * 0.4;
    rHeight = width * 0.6;
    // 棋盘各个点
    // 第一行,从左到右
    boardOffsetList.add(Offset(-rWidth, -rHeight));
    boardOffsetList.add(Offset(0, -rHeight));
    boardOffsetList.add(Offset(rWidth, -rHeight));
    // 第二行,从左到右
    boardOffsetList.add(Offset(-rWidth, 0));
    boardOffsetList.add(Offset.zero);
    boardOffsetList.add(Offset(rWidth, 0));
    // 第二行,从左到右
    boardOffsetList.add(Offset(-rWidth, rHeight));
    boardOffsetList.add(Offset(0, rHeight));
    boardOffsetList.add(Offset(rWidth, rHeight));

在自定义的 DiagonalChessPainter 中进行绘制,先绘制一个矩形,然后绘制四条对角线完成整个棋盘的绘制,代码如下:

    // 绘制矩形
    canvas.drawRect(
        Rect.fromLTRB(-rWidth, -rHeight, rWidth, rHeight), _chessboardPaint);
    // 绘制对角线
    Path path = Path()
      // P1-P9
      ..moveTo(boardOffsetList[0].dx, boardOffsetList[0].dy)
      ..lineTo(boardOffsetList[8].dx, boardOffsetList[8].dy)
      // P2-P8
      ..moveTo(boardOffsetList[1].dx, boardOffsetList[1].dy)
      ..lineTo(boardOffsetList[7].dx, boardOffsetList[7].dy)
      // P3-P7
      ..moveTo(boardOffsetList[2].dx, boardOffsetList[2].dy)
      ..lineTo(boardOffsetList[6].dx, boardOffsetList[6].dy)
      // P4-P6
      ..moveTo(boardOffsetList[3].dx, boardOffsetList[3].dy)
      ..lineTo(boardOffsetList[5].dx, boardOffsetList[5].dy);
    canvas.drawPath(path, _chessboardPaint);

棋盘展示效果:

2. 绘制棋子

我们先定义6个棋子,并添加必要的绘制用到的属性。代码如下:

 // 定义棋子位置、颜色、文案
    piecesOffsetList.clear();
    piecesOffsetList
        .add(PiecesBean(boardOffsetList[0], Colors.greenAccent, "1"));
    piecesOffsetList
        .add(PiecesBean(boardOffsetList[1], Colors.greenAccent, "2"));
    piecesOffsetList
        .add(PiecesBean(boardOffsetList[2], Colors.greenAccent, "3"));
    piecesOffsetList.add(PiecesBean(boardOffsetList[6], Colors.redAccent, "1"));
    piecesOffsetList.add(PiecesBean(boardOffsetList[7], Colors.redAccent, "2"));
    piecesOffsetList.add(PiecesBean(boardOffsetList[8], Colors.redAccent, "3"));

关于棋子的绘制,这里为了简化,绘制一个简单的圆+序号文案即可。

  /// 绘制单个棋子
  void _drawChessPiece(
      Canvas canvas, PiecesBean bean, bool reverse, bool isSelected) {
    var offset = bean.offset;
    var color = bean.color;
    double radius = 25;
    canvas.save();
    canvas.translate(offset.dx, offset.dy);
    canvas.drawCircle(Offset.zero, radius, _chessPiecesPaint..color = color);
    _drawChessPieceText(canvas, bean, isSelected);
    canvas.restore();
  }

文案的绘制。通过TextPainter进行绘制,绘制时注意先textPainter.layout()测量后再计算偏移量。

 var textPainter = TextPainter(
      text: TextSpan(
          text: bean.text,
          style: TextStyle(
            fontSize: isSelected ? 35 : 30,
            color: Colors.white,
            fontWeight: FontWeight.bold,
          )),
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    var textSize = textPainter.size;
    textPainter.paint(
        canvas, Offset(textSize.width * -0.5, textSize.height * -0.5));
  // 定义步数,判断那一方走下一步棋
  int step = 0;

棋子展示效果:

3. 手势处理

通常我们下棋时,首先点击某个棋子,然后点击需要移动到的位置。此时,棋子先变成选中状态,然后移动到选中的位置,完成棋子的移动。

对于手势的处理,Flutter通过GestureDetector来实现。我们先定义GestuerDetecotr,将child设为CustomPiant,我们在onTapDown中处理用户点击。代码如下:

GestureDetector(
            onTapDown: _onTapDown,
            child: CustomPaint(
              size: Size(width, height),
              painter: DiagonalChessPainter(),
            ),
          );

通过手势点击的位置和棋子的位置进行比较即可判断当前是否点击的是棋子。代码如下:

 var offset = details.globalPosition;
    var dx = offset.dx - width * 0.5;
    var dy = offset.dy - height * 0.5;
    for (MapEntry<int, PiecesBean> entry in piecesOffsetList.asMap().entries) {
      var bean = entry.value;
      var index = entry.key;
      var piecesOffset = bean.offset;
      if (_checkPoint(piecesOffset.dx, piecesOffset.dy, dx, dy)) {
        // 更新棋子选中状态
        piecesIndex.value = index;
        // debugPrint("piecesIndex:$piecesIndex");
        return;
      }
    }
  /// 是否是当前点
  bool _checkPoint(double dx1, double dy1, double dx2, double dy2) =>
      (dx1 - dx2).abs() < 40 && (dy1 - dy2).abs() < 40;

若判断当前不是点击的棋子,则判断是否点击的棋盘中9个点的位置,若是则判断是否已选中棋子,若选中则修改棋子的Offset重新绘制。代码如下:

    // 若点击是棋盘
    for (MapEntry<int, Offset> entry in boardOffsetList.asMap().entries) {
      var offset = entry.value;
      var index = entry.key;
      if (_checkPoint(offset.dx, offset.dy, dx, dy)) {
        if (piecesIndex.value > -1) {
          var bean = piecesOffsetList[piecesIndex.value];
          bean.offset = boardOffsetList[index];
        }
        // debugPrint("boardsIndex:$index");
        return;
      }
    }

实现效果如下:

4. 游戏规则

1. 棋子移动规则

我们下棋时,每一方只能走一步交替进行下棋,且棋子只能按照棋盘规则行走。代码如下:

 // 棋盘各个点可移动位置
  final moveVisibleList = [
    [1, 3, 4],
    [0, 2, 4],
    [1, 4, 5],
    [0, 4, 6],
    [0, 1, 2, 3, 5, 6, 7, 8],
    [2, 4, 8],
    [3, 4, 7],
    [4, 6, 8],
    [4, 5, 7], //第9个点可移动位置
  ];

我们分别在点击棋子和棋盘位置时判断是否当前一方的棋子走,若是当前棋子是否可以走到该棋盘位置。代码如下:

 // 更新棋子选中状态
        if (step % 2 == 1 && index < 3 || step % 2 == 0 && index >= 3) {
          piecesIndex.value = index;
        }
 // 判断棋子是否可以走到该位置
        if (piecesIndex.value > -1 &&
            isMoveViable(piecesIndex.value, index) &&
            (step % 2 == 1 && piecesIndex.value < 3 ||
                step % 2 == 0 && piecesIndex.value >= 3)) {
          var bean = piecesOffsetList[piecesIndex.value];
          bean.offset = boardOffsetList[index];
          boardIndex.value = index;
          step++;
        }

2. 比赛胜利规则

我们首先根据对角棋的胜利规则定义比赛胜利需要移动到的位置。代码如下:

  // 胜利的位置
  final winPositions = [
    [0, 4, 8],
    [2, 4, 6]
  ];

在棋子每次发生移动后来校验当前棋子是否匹配胜利的位置,若匹配则弹窗提示胜利方。代码如下:

/// 获取胜利的状态
  int getWinState() {
    for (int i = 0; i < piecesOffsetList.length / 3; i++) {
      var offset1 = piecesOffsetList[i * 3 + 0].offset;
      var offset2 = piecesOffsetList[i * 3 + 1].offset;
      var offset3 = piecesOffsetList[i * 3 + 2].offset;
      if (isWinPosition(offset1, offset2, offset3)) {
        return i;
      }
    }
    return -1;
  }
  /// 是否是符合胜利的位置
  bool isWinPosition(Offset offset1, Offset offset2, Offset offset3) {
    var position1 = boardOffsetList.indexOf(offset1);
    var position2 = boardOffsetList.indexOf(offset2);
    var position3 = boardOffsetList.indexOf(offset3);
    for (var positionList in winPositions) {
      if (positionList.contains(position1) &&
          positionList.contains(position2) &&
          positionList.contains(position3)) {
        return true;
      }
    }
    return false;
  }
  /// 判断是否有一方胜利
  void checkWinState() {
    var winState = getWinState();
    switch (winState) {
      case 0: // 绿色方胜利
        _showDialogTip("绿色方胜利!");
        break;
      case 1: // 红色放胜利
        _showDialogTip("红色方胜利!");
        break;
      default:
        break;
    }
  }

最后,当一方无法走下一步时,自动判断另外一方胜利。代码如下:

 /// 获取胜利的状态
  int getWinState() {
    for (int i = 0; i < piecesOffsetList.length / 3; i++) {
      var index1 = piecesOffsetList[i * 3 + 0].boardIndex;
      var index2 = piecesOffsetList[i * 3 + 1].boardIndex;
      var index3 = piecesOffsetList[i * 3 + 2].boardIndex;
      var lastIndex = piecesOffsetList.length - 1;
      var otherIndex1 = piecesOffsetList[lastIndex - (i * 3 + 0)].boardIndex;
      var otherIndex2 = piecesOffsetList[lastIndex - (i * 3 + 1)].boardIndex;
      var otherIndex3 = piecesOffsetList[lastIndex - (i * 3 + 2)].boardIndex;
      // 判断一方是否已胜利
      if (isWinPosition(index1, index2, index3)) {
        return i;
      }
      // 判断另外一方是否已无法走棋
      if (isOtherNotMoveVisible(
          [index1, index2, index3], [otherIndex1, otherIndex2, otherIndex3])) {
        return i;
      }
    }
    return -1;
  }
/// 另一方是否无法走下一步
bool isOtherNotMoveVisible(List<int> list1, List<int> list2) {
    List<int> list = [...list1, ...list2];
    for (var index in list2) {
      for (var moveIndex in moveVisibleList[index]) {
        if (!list.contains(moveIndex)) {
          return false;
        }
      }
    }
    return true;
  }

至此,我们完成了整个游戏的实现!✿✿ヽ(°▽°)ノ✿

优化

上面已经把对角棋游戏的整个功能都实现了,但仔细思考还是有可以优化的点。

1. 对手视角棋子调整

前面我们都是以自己的视角来实现棋子,但实际使用时对手应该对方的视角来观察。因此,我们需要把对手的棋子顺序和文案进行倒序处理。代码如下:

 // 对手棋子倒序显示
 piecesOffsetList
        .add(PiecesBean(boardOffsetList[0], Colors.greenAccent, "3"));
    piecesOffsetList
        .add(PiecesBean(boardOffsetList[1], Colors.greenAccent, "2"));
    piecesOffsetList
        .add(PiecesBean(boardOffsetList[2], Colors.greenAccent, "1"));
 /// 绘制单个棋子
  void _drawChessPiece(
      Canvas canvas, PiecesBean bean, bool reverse, bool isSelected) {
    ...
    canvas.save();
    canvas.translate(offset.dx, offset.dy);
    // 对手棋子旋转180度,文案倒序显示
    if (reverse) canvas.rotate(pi);
    ...
    canvas.restore();
  }

2. CustomPainter刷新机制优化

正常我们使用setStatus进行Widget刷新,但考虑到我们只需要对 CustomPainter 进行刷新,我们可以使用 Listenable 对象来控制画布的刷新,这样是最高效的方式。对于多个 Listenable 对象使用 Listenable.merge 来合并。代码如下:

// 选择棋子序号
ValueNotifier&lt;int&gt; piecesIndex = ValueNotifier&lt;int&gt;(-1);
// 点击棋盘位置
ValueNotifier&lt;int&gt; boardIndex = ValueNotifier&lt;int&gt;(-1);
CustomPaint(
  size: Size(width, height),
  painter: DiagonalChessPainter(
    ...
    piecesIndex: piecesIndex,
    boardIndex: boardIndex,
    repaint: Listenable.merge([piecesIndex, boardIndex]),
  ),
)
 @override
 bool shouldRepaint(covariant DiagonalChessPainter oldDelegate) {
   return oldDelegate.repaint != repaint;
 }

总结

虽然对角棋看起来非常简单,但我们完全实现却没有那么容易。中间用到了 Canvas 的 translate 、rotate 、save/restore 、矩形 线段 文本 的绘制、CustomPainter 的 Listenable 对象刷新、手势的处理等知识,算是对 Canvas 的绘制有一个大概的回顾。

实践出真知!看十遍相关资料不如敲一遍代码。后续我也会继续出相关系列文章,如果大家喜欢的话,请关注一下吧!

最后附上 项目源码地址

以上就是Flutter开发之对角棋游戏实现实例详解的详细内容,更多关于Flutter 对角棋游戏的资料请关注我们其它相关文章!

(0)

相关推荐

  • Flutter组件开发过程完整讲解

    首先统一一个概念,不管是component(组件),widget(控件),module(android的模块)在我的理解能力范围内,都是为了抽离某个特定的功能,对外接受参数,对内实现功能的某一个代码块. 它是一个颗粒化的实体,是相对于建筑物的钢筋,水泥,砖头.他们个有特点,相互独立,各有特性,同时又提供了某种可以内聚融合的对外接口.component(组件),widget(控件)下面都统称组件,对component,widget有不同理解的朋友,希望能在评论区收到你们的见解和建议. 在前端开发中

  • Flutter随机迷宫生成和解迷宫小游戏功能的源码

    此博客旨在帮助大家更好的了解图的遍历算法,通过Flutter移动端平台将图的遍历算法运用在迷宫生成和解迷宫上,让算法变成可视化且可以进行交互,最终做成一个可进行随机迷宫生成和解迷宫的APP小游戏.本人是应届毕业生,希望能与大家一起讨论和学习- 注:由于这是本人第一次写博客,难免排版或用词上有所欠缺,请大家多多包涵. 注:如需转载文章,请注明出处,谢谢. 一.项目介绍: 1.概述 项目名:方块迷宫 作者:沫小亮. 编程框架与语言:Flutter&Dart 开发环境:Android Studio 3

  • 利用Flutter制作经典贪吃蛇游戏

    目录 前言 使用 Flutter 作为游戏引擎 画蛇 2D 渲染的基础 创建蛇 填写列表 将蛇移动到下一个位置 添加运动和速度 添加控件 改变方向 吃东西和提高速度 在屏幕上显示食物 消耗和再生食物 检测碰撞并显示游戏结束对话框 添加一些收尾工作 重启游戏 显示分数 前言 Flutter (Channel stable, 2.10.3, on Microsoft Windows [Version 10.0.19044.1586], locale zh-CN) Android toolchain

  • 基于Android Flutter编写贪吃蛇游戏

    目录 前言 开发步骤: 1.定义蛇和豆子 2.让蛇动起来 3.使用陀螺仪来控制蛇 4.让蛇吃掉豆子 5.吃掉豆子随机生成一个豆子 前言 放假期间,小T打算回顾一下经典,想用Flutter做一下小游戏,做什么好呢,从打飞机到坦克大战,最后还是想做一款贪吃蛇,依稀还记得,小时候第一次玩游戏是在父母的小灵通上玩贪吃蛇哈哈,但是光光一个贪吃蛇太单调了,我们就加一个陀螺仪吧~ 话不多说,先上效果图,有图有真相!!(陀螺仪好难操控)! 开发步骤: 非常简单,就是玩起来有点费手~ github仓库还没有搭建,

  • Flutter桌面开发windows插件开发

    目录 前言 插件介绍 windows插件编写 Windows插件的一些坑 前言 通过此篇文章,你将了解到: Flutter插件的基本介绍: windows插件开发的真实踩坑经验. 我们都知道,Flutter的定位更多是作为一个跨平台的UI框架,对于原生平台的功能,开发过程中经常需要插件来提供.不幸的是Windows的生态又极其不完整,插件开发必不可少.但网上windows的文章少之又少,所以本篇文章,我们一起来聊聊插件开发的一些技巧. 插件介绍 Flutter的插件主要分两种:package和p

  • Flutter开发setState能否在build中直接调用详解

    目录 两种情况 原理分析 总结 两种情况 setState() 能在 build() 中直接调用吗?答案是能也不能. 来看一段简单的代码: import 'package:flutter/material.dart'; class TestPage extends StatefulWidget { const TestPage({super.key}); @override State<TestPage> createState() => _State(); } class _State

  • Flutter开发Widgets 之 PageView使用示例

    目录 构造方法以及参数: 基本用法 无限滚动 实现指示器 切换动画 总结: 构造方法以及参数: PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,如APP第一次安装时的引导页面,也可用于开发轮播图功能. PageView({ Key? key, this.scrollDirection = Axis.horizontal, // 设置滚动方向 垂直 / 水平 this.reverse = false, // 反向滚动 Pag

  • Flutter开发之对角棋游戏实现实例详解

    目录 前沿 演示效果 对角棋规则 实现思路 具体实现 1. 绘制棋盘 2. 绘制棋子 3. 手势处理 4. 游戏规则 优化 总结 前沿 关于对角棋相信大家都不陌生,其凭借着规则简单又灵活多变成为我们童年不可缺少的益智游戏. 今天我将用Flutter来实现一个对角棋游戏,即巩固自己Flutter的绘制和手势知识,也希望这篇文章对大家有所帮助. 演示效果 老规矩,我们先演示下实现的最终效果: 对角棋规则 首先我们还是回顾下对角棋游戏的规则,这里借用 百度百科 的规则说明: 棋盘:象棋棋盘中,将士所在

  • IOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解 在实际的开发需求时,有时候我们需要对某些对象进行打包,最后拼接到参数中 例如,我们把所有的参数字典打包为一个 字符串拼接到参数中 思路:利用系统系统JSON序列化类即可,NSData作为中间桥梁 //1.字典转换为字符串(JSON格式),利用 NSData作为桥梁; NSDictionary *dic = @{@"name":@"Lisi",@"sex":@"m",@"tel&qu

  • Swift 开发之懒加载的实例详解

    Swift 开发之懒加载的实例详解 /// A display link that keeps calling the `updateFrame` method on every screen refresh. private lazy var displayLink: CADisplayLink = { self.isDisplayLinkInitialized = true let displayLink = CADisplayLink(target: TargetProxy(target:

  • JSP开发中Apache-HTTPClient 用户验证的实例详解

    JSP开发中Apache-HTTPClient 用户验证的实例详解 前言: 在微服务框架之外的系统中,我们经常会遇到使用httpClient进行接口调用的问题,除了进行白名单的设置,很多时候我们需要在接口调用的时候需要身份认证.翻了一下官方文档,解决方法很多,但是都不太符合实际业务场景,这里提供一种简单粗暴的解决方法. 解决方法:利用请求头,将验证信息保存起来. 实现代码: public class HttpClientUtils { protected static final Logger

  • Android开发高仿课程表的布局实例详解

    先说下这个demo,这是一个模仿课程表的布局文件,虽然我是个菜鸟,但我还是想留给学习的人一些例子,先看下效果 然后再来看一下我们学校的app 布局分析 先上一张划分好了的布局图 首先整个页面放在一个LinearLayout布局下面,分为上面和下面两个部分,下面一个是显示课程表的详细信息 1:这个没什么好讲的,就是直接一个LinearLayout布局,然后将控件一个TextView用来显示年份,一个View用来当作竖线,一个Spinner用来显示选择周数 2:这个是显示星期几的部件,是我自定义的V

  • Go开发Gin项目添加jwt功能实例详解

    目录 啥是JWT 为什么要用在你的Gin中使用JWT JWT的基本原理 JWT TOKEN怎么组成 Header Base64URL Payload Signature 解密过程 一些特点(优点和缺点) GIN整合JWT 编写jwtutil GenToken方法 ParseToken方法 编写中间件 使用中间件 测试 其他 啥是JWT JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场

  • vue开发设计分支切换与cleanup实例详解

    目录 分支切换与cleanup 了解分支切换 分支切换导致的问题 如何清除掉副作用函数的无效关联关系? 疑问:为什么对传入的副作用函数进行一层包裹? 完整代码 产生的问题:代码运行发生栈溢出 如何解决此种情况下的栈溢出? 嵌套的effect与effect栈 effect嵌套的场景? 初始化 原因: 支持嵌套 避免无限递归循环 产生无限递归循环的代码: 原因分析: 解决循环 完整代码 分支切换与cleanup 了解分支切换 代码示例如下 const data = { ok: true, text:

  • flutter开发技巧自定页面指示器PageIndicator详解

    目录 一.来源 二.效果 三.源码实现 1.flutter_swiper_null_safety 使用示例: 2.PageIndicatorWidget 指示器源码: 三.总结 一.来源 项目中遇到多个需要自定义轮播图指示器的需求,封装成基础组件方便复用: 原理是通过 ValueListenableBuilder 实时监听轮播图的当前索引,然后更新指示器组件,达到最终效果: 二.效果 三.源码实现 1.flutter_swiper_null_safety 使用示例: import 'packag

  • JS学习笔记之贪吃蛇小游戏demo实例详解

    本文实例讲述了JS学习笔记之贪吃蛇小游戏demo实例.分享给大家供大家参考,具体如下: 最近跟着视频教程打了一个贪吃蛇, 来记录一下实现思路, 先上代码 静态页 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>贪吃蛇</title> </head> <style> *{ mar

  • Android开发中的重力传感器用法实例详解

    本文实例讲述了Android开发中的重力传感器用法.分享给大家供大家参考,具体如下: 重力传感器与方向传感器的开发步骤类似,只要理清了期中的x,y,z的值之后就可以根据他们的变化来进行编程了,首先来看一副图 假设当地的重力加速度值为g 当手机正面朝上的时候,z的值为q,反面朝上的时候,z的值为-g 当手机右侧面朝上的时候,x的值为g,右侧面朝上的时候,x的值为-g 当手机上侧面朝上的时候,y的值为g,右侧面朝上的时候,y的值为-g 了解了重力传感器中X,Y,Z的含义之后下面我们就开始学习如何使用

随机推荐