小言_互联网的博客

HaaS EDU场景式应用学习 - 飞机大战

382人阅读  评论(0)

1、实验介绍

飞机大战作为一款经典的街机游戏,是很多人的童年回忆。我们的 HaaS EDU K1 开发板专门设计了街机样式的按键排列,很适合我们做这类游戏的开发。

 

2、涉及知识点

OLED绘图

按键事件

 

3、软硬件环境准备

3.1、硬件

开发用电脑一台

HAAS EDU K1 开发板一块

USB2TypeC 数据线一根

 

3.2、软件

"飞机大战"功能已经包含在edu_demo应用中,并且包含在发布版本中。

3.2.1、固件版本

固件版本:V1.0.0

3.2.2、代码路径


  
  1. git clone https: //gitee.com/alios-things/AliOS-Things.git -b dev_3.1.0_haas
  2. cd AliOS-Things /application/example /edu_demo/k1_apps/aircraftBattle

3.2.3、编译

进入代码的顶层目录如AliOS-Things进行编译。直接编译application/example/目录下的edu_demo应用。

两种方法进行编译

命令行方式


  
  1. aos make distclean
  2. aos make edu_demo@haaseduk1 -c config
  3. aos make

AliOS Studio IDE方式

3.2.4、烧录

见开发环境章节

 

4、游戏设定

不同于规则简单的贪吃蛇,在飞机大战这类游戏中,往往需要对游戏中出现的每个对象进行数值、行为的设定。在开发游戏前期,梳理好这些设定也有助于我们更清晰地进行开发。有时,优秀的设定也是吸引玩家的重要因素。

4.1、角色设定

 

 

 

4.2、行为设定

  • 在本游戏中,玩家将控制阿克琉斯级战舰,在持续不断的敌机中通过闪避或攻击开辟出自己的路。
  • 玩家可以通过 HaaS EDU K1 的四个按键控制 阿克琉斯级战舰 进行前后左右运动。
  • 在游戏进行过程中,玩家的战舰会不断发射炮弹。被炮弹攻击的敌方战舰会损失响应的装甲。
  • 若玩家战舰被敌方战舰撞击,双方均会损失装甲。
  • 玩家有三次紧急修复战舰的机会。

 

5、游戏实现

5.1、游戏流程

在开始之前,我们先使用一个简单的流程,帮助大家理解本游戏的刷新机制。这个大循化即游戏刷新所需要的所有流程。


  
  1. // 游戏中所有对象的更新判定由大循环维护
  2. void aircraftBattle_task()
  3. {
  4.     while ( 1)
  5.     {
  6.         OLED_Clear();       // 清理屏幕数据
  7.         global_update();    // 刷新全局对象 如更新对象的贴图状态 发射子弹 撞击判断 等
  8.         global_draw();      // 绘制刷新完后的所有对象
  9.         OLED_Refresh_GRAM(); // 将绘制结果显示在屏幕上
  10.         aos_msleep( 40);     // 40ms 为一个游戏周期
  11.     }
  12. }

5.2、贴图实现

对于每个对象,我们希望能够将其定位到游戏地图上的每一点,而不是单纯使用贴图函数。因此,每个对象有一个“控制坐标”,而我们相对这个“控制坐标”计算出贴图坐标。这样,如果一个对象需要变换不同尺寸的贴图,我们可以更方便地计算出它的贴图坐标。

如图,红色为该对象的控制坐标,蓝色为该贴图的贴图坐标。

               


  
  1. typedef struct
  2. {
  3.     map_t * map;             // 贴图
  4.     int cur_x;
  5.     int cur_y;              // 飞行物对象的控制坐标
  6. } dfo_t;                    // 飞行物对象

  
  1. /*
  2.     -> x
  3.     ____________________
  4.     |         |    icon|
  5.  |  |       of_y       |
  6.  \/ |         |        |
  7.   y |--of_x--cp        |
  8.     |__________________|
  9. */
  10. typedef struct
  11. {
  12.     icon_t *icon;   // 贴图对象
  13.     int offset_x;
  14.     int offset_y;   // 相对于控制坐标的偏移
  15. } map_t; // 贴图

注意,在开发过程中,我们使用的是竖屏模式,坐标系是以竖屏做处理。因此,在绘图时,我们需要做坐标系的转换。


  
  1. void draw_dfo(dfo_t *dfo)
  2. {
  3.     map_t *cur_map = get_cur_map(dfo);  // 获取当前对象的贴图
  4.    
  5.     // 计算对象边界
  6.     int top = dfo->cur_y + cur_map->offset_y;  
  7.     int bottom = dfo->cur_y + cur_map->offset_y + cur_map->icon->width;
  8.     int left = dfo->cur_x + cur_map->offset_x;
  9.     int right = dfo->cur_x + cur_map->offset_x + cur_map->icon->height;
  10.    
  11.     // 若对象超出屏幕 则不绘制
  12.     if (top > 132 || bottom < 0 || left > 64 || right < 0)
  13.         return;
  14.     // 绘制坐标转换后的贴图对象
  15.     OLED_Icon_Draw(
  16.         dfo->cur_y + cur_map->offset_y,
  17.         64 - (dfo->cur_x + cur_map->offset_x + cur_map->icon->height),
  18.         cur_map->icon,
  19.         2);
  20. }

这样,就可以实现在OLED上绘制我们设定的战舰图片了。

5.3、移动战舰

接下来,我们要实现的是根据用户的按键输入来移动战舰的贴图。在此之前,我们需要对 dfo_t 结构体进行更多的补充。我们额外定义一个 speed 属性,用于定义在用户每次操作时移动一定的距离。

注意,这里的前后左右均是在游戏坐标系中。


  
  1. typedef struct
  2. {
  3.     // 舰船坐标
  4.     int cur_x; // 运动
  5.     int cur_y;
  6.     // 舰船速度
  7.     uint8_t speed;      // 绝对固定
  8.     // 舰船贴图
  9.     map_t *map;
  10. } dfo_t;                   // Dentified Flying Object
  11. typedef enum
  12. {
  13.     UP,
  14.     LEFT,
  15.     RIGHT,
  16.     DOWN
  17. } my_craft_dir_e_t;
  18. void move_MyCraft(dfo_t *my_craft, my_craft_dir_e_t dir)
  19. {
  20.     // 获取舰船当前的贴图对象
  21.     map_t *cur_map = get_cur_map(my_craft);
  22.     // 计算贴图边界
  23.     int top = my_craft->cur_y + cur_map->offset_y;
  24.     int bottom = my_craft->cur_y + cur_map->offset_y + cur_map->icon->width;
  25.     int left = my_craft->cur_x + cur_map->offset_x;
  26.     int right = my_craft->cur_x + cur_map->offset_x + cur_map->icon->height;
  27.     // 判断方向
  28.     switch (dir)
  29.     {
  30.     case UP:
  31.         // 如果这次移动不会超过地图边界 则移动
  32.         if (!(top - my_craft->speed < 0))
  33.             my_craft->cur_y -= my_craft->speed;
  34.         break;
  35.     case DOWN:
  36.         if (!(bottom + my_craft->speed > 132))
  37.             my_craft->cur_y += my_craft->speed;
  38.         break;
  39.     case LEFT:
  40.         if (!(left - my_craft->speed < 0))
  41.             my_craft->cur_x -= my_craft->speed;
  42.         break;
  43.     case RIGHT:
  44.         if (!(right + my_craft->speed > 64))
  45.             my_craft->cur_x += my_craft->speed;
  46.         break;
  47.     default:
  48.         break;
  49.     }
  50. }

将按键回调函数关联至移动舰船函数。注意,这里的前后左右均是在游戏坐标系中。


  
  1. void aircraftBattle_key_handel(key_code_t key_code)
  2. {
  3.     switch (key_code)
  4.     {
  5.     case EDK_KEY_4:
  6.         move_MyCraft(my_craft, LEFT);
  7.         break;
  8.     case EDK_KEY_1:
  9.         move_MyCraft(my_craft, UP);
  10.         break;
  11.     case EDK_KEY_3:
  12.         move_MyCraft(my_craft, DOWN);
  13.         break;
  14.     case EDK_KEY_2:
  15.         move_MyCraft(my_craft, RIGHT);
  16.         break;
  17.     default:
  18.         break;
  19.     }
  20. }

5.4、添加特效

作为一个注重细节,精益求精的开发者,我们希望给我们的舰船加上一些特效。而这需要舰船对象不断改变重绘自己的贴图。为了这个功能,我们额外创建了一个新的结构体用于管理“动画”。


  
  1. typedef struct
  2. {
  3.     map_t **act_seq_maps;           // 贴图指针数组 该动画的所有贴图(例如爆炸动作包含3帧)
  4.     uint8_t act_seq_len;            // 贴图指针数组长度
  5.     uint8_t act_seq_index;           // 用于索引帧
  6.    
  7.     uint8_t act_seq_interval;       // 帧间延迟
  8.     uint8_t act_seq_interval_cnt;   // 用于延迟计数
  9.    
  10.     uint8_t act_is_destory;         // 用于标记该动画是否是毁灭动画 若是则不再重复
  11. } act_seq_t;

同时,每个舰船对象新增了一系列属性 act_seq_type, 用于显示当前的贴图状态。例如,当 act_seq_type = 0 时,表示舰船处于正常状态,每隔 act_seq_interval 个周期切换显示一次贴图,即第一行的三帧贴图。当 act_seq_type = 1 时,表示舰船处于爆炸状态,每隔 act_seq_interval 个周期切换显示一次贴图,即第二行的三帧贴图。

目前 act_seq_type 的含义由每个舰船对象自己定义和维护。也可以归纳成统一的枚举量,这一步读者可以自行完成。


  
  1. typedef struct
  2. {
  3.     int cur_x;
  4.     int cur_y;
  5.     uint8_t speed;
  6.     act_seq_t **act_seq_list; // 动画数组 包含了多个动作序列
  7.     uint8_t act_seq_list_len; // 动画数组长度
  8.     uint8_t act_seq_type;
  9. } dfo_t;
  10. // 正常动作序列   
  11. act_seq_t *achilles_normal_act = ( act_seq_t *) malloc( sizeof( act_seq_t));
  12. achilles_normal_act->act_seq_maps = achilles_normal_maplist;
  13. achilles_normal_act->act_seq_len = 3;        // 该动作序列包含3帧图片
  14. achilles_normal_act->act_seq_interval = 10; // 该动画帧间延迟10周期
  15. achilles_normal_act->act_is_destory = 0;    // 该动画不是毁灭动画 即一直重复
  16. // 毁灭动作序列   
  17. act_seq_t *achilles_destory_act = ( act_seq_t *) malloc( sizeof( act_seq_t));
  18. achilles_destory_act->act_seq_maps = achilles_destory_maplist;
  19. achilles_destory_act->act_seq_len = 3;
  20. achilles_destory_act->act_seq_interval = 4; // 该动画帧间延迟4周期
  21. achilles_destory_act->act_is_destory = 1;
  22. // 动作序列数组
  23. act_seq_t **achilles_act_seq_list = ( act_seq_t **) malloc( sizeof( act_seq_t *) * achilles->act_seq_list_len);
  24. achilles_act_seq_list[ 0] = achilles_normal_act;
  25. achilles_act_seq_list[ 1] = achilles_destory_act;
  26. // 将舰船对象属性指向该动作序列数组
  27. achilles->act_seq_list = achilles_act_seq_list;
  28. achilles->act_seq_type = 0;

定义完成后,我们需要在游戏的每一次循环中,更新战舰状态和贴图。


  
  1. void craft_update_act(dfo_t *craft)
  2. {
  3.     act_seq_t *cur_act_seq = craft->act_seq_list[craft->act_seq_type];
  4.     if (cur_act_seq->act_seq_interval == 0)
  5.         return; // 若当前战舰无动作序列,则不进行更新
  6.     ++(cur_act_seq->act_seq_interval_cnt);
  7.     if (cur_act_seq->act_seq_interval_cnt >= cur_act_seq->act_seq_interval)
  8.     {
  9.         cur_act_seq->act_seq_interval_cnt = 0;
  10.         ++(cur_act_seq->act_seq_index); // 切换贴图
  11.         if (cur_act_seq->act_seq_index >= cur_act_seq->act_seq_len)
  12.         {
  13.             cur_act_seq->act_seq_index = 0;
  14.             if (cur_act_seq->act_is_destory == 1)
  15.             {
  16.                 // 在这里处理毁灭的舰船
  17.             }
  18.         }
  19.     }
  20. }

这样,我们就为战舰添加了喷气的特效。

5.5、移动敌机

移动敌机的方式更简单。只需要将其向下移动即可。实现方式如下。


  
  1. void move_enemy(dfo_t *craft)
  2. {
  3.     map_t *cur_map = get_cur_map(craft);
  4.     craft->cur_y += craft->speed;
  5.     int top = craft->cur_y + cur_map->offset_y;
  6.     if (top > 132)                                      // 当敌机飞过屏幕下方
  7.         reload_dfo(craft, AUTO_RELOAD, AUTO_RELOAD);    // 重载敌机
  8. }

5.6、重载敌机

在飞机大战中,会有持续不断的敌机生成,并且敌机的出现顺序和位置都随机。为了实现这种效果,我们采用的方式是维护一个敌机数组,当敌机飞过屏幕下方或是被击落后,我们会回收敌机并重新加载,将其重新显示在屏幕上。


  
  1. void reload_dfo(dfo_t *craft, int pos_x, int pos_y)
  2. {
  3.     craft->cur_x = craft->pos_x;
  4.     craft->cur_y = craft->pos_y;
  5.    
  6.     if (pos_x == AUTO_RELOAD)   // 如果指定重载坐标为自动重载
  7.     {
  8.         uint16_t height = get_cur_map(craft)->icon->width;
  9.         craft->cur_x = random() % ( 64 - height) + height / 2;   // 则随机生成一个坐标,且保证对象显示在地图内
  10.     }
  11.     if (pos_y == AUTO_RELOAD)
  12.     {
  13.         uint16_t width = get_cur_map(craft)->icon->height;
  14.         craft->cur_y = -(random() % 1000) - width / 2;
  15.     }
  16. }

这样,就能够实现源源不断的敌机了。

 

5.7、发射子弹

对于子弹而言,它和战舰的属性非常相似,因此我们在现有的舰船对象 dfo_t 上稍加改动即可。


  
  1. typedef enum
  2. {
  3.     Achilles,   // 阿克琉斯级
  4.     Venture,    // 冲锋者级
  5.     Ares,       // 阿瑞斯级 战神级
  6.     TiTan,      // 泰坦级
  7.     Bullet,     // 子弹
  8. } dfo_model_e_t;    // 飞行物型号
  9. typedef struct
  10. {
  11.     int offset_x;
  12.     int offset_y;   // 炮台的相对位置
  13. } arms_t;           // 武装结构体
  14. typedef struct
  15. {
  16.     dfo_model_e_t model;    // 型号
  17.    
  18.     // 运动相关
  19.     int start_x;            // 飞行物的起始位置 用于计算飞行距离
  20.     int start_y;
  21.     int cur_x;              // 飞行物的当前位置
  22.     int cur_y;
  23.     uint8_t speed;          // 飞行物的运动速度
  24.     unsigned int range;     // 射程
  25.     // 显示相关
  26.     act_seq_t **act_seq_list;   // 动画数组
  27.     uint8_t act_seq_list_len;   // 动画数组长度
  28.     uint8_t act_seq_type;       // 动画状态
  29.     // 攻击相关
  30.     arms_t **arms_list;    // 武器装备数组
  31.     uint8_t arms_list_len; // 武器数组长度
  32. } dfo_t;

那么,目前 dfo_t 结构体不仅仅可以用于舰船,也可以用于定义子弹。接下来,我们为舰船定义炮台和子弹。


  
  1. dfo_t *create_achilles()    // 定义阿克琉斯级战舰
  2. {
  3.     // 贴图等其他定义
  4.     achilles->damage = 8;       // 定义撞击伤害
  5.     achilles->full_life = 10;   // 定义完整装甲值
  6.     achilles->cur_life = 10;    // 初始化装甲值
  7.     achilles->arms_list_len = 2;    // 设定炮台数为2
  8.     achilles->arms_list = achilles_arms_list;   // 定义炮台数组
  9.     return achilles;
  10. }
  11. dfo_t *create_bullet()
  12. {
  13.     // 贴图等其他定义 
  14.     bullet->damage = 1;         // 定义射击伤害
  15.     bullet->full_life = 1;      // 定义完整装甲值
  16.     bullet->cur_life = 0;       // 初始化子弹时 默认不激活
  17.     bullet->start_x = -100;     // 初始化子弹时 将其移出屏幕外不做处理
  18.     bullet->start_y = -100;
  19.     bullet->cur_x = -100;
  20.     bullet->cur_y = -100;
  21.     return bullet;
  22. }

为了生成持续不断的子弹,我们也采用重载的方式去生成子弹。


  
  1. // 检索未被激活的子弹
  2. dfo_t *get_deactived_bullet()
  3. {
  4.     for ( int i = 0; i < MAX_BULLET; i++)
  5.     {
  6.         if (bullet_group[i]->cur_life <= 0)
  7.             return bullet_group[i];
  8.     }
  9.     return NULL;
  10. }
  11. // 触发舰船射击子弹
  12. void shut_craft(dfo_t *craft)
  13. {
  14.     if (craft->arms_list == NULL || craft->arms_list_len == 0)
  15.         return;
  16.     // 从每个炮台重载子弹
  17.     for ( int i = 0; i < craft->arms_list_len; i++)
  18.     {
  19.         dfo_t *bullet = get_deactived_bullet();
  20.         if (bullet == NULL)
  21.             return;
  22.         reload_dfo(bullet, craft->cur_x + craft->arms_list[i]->offset_x, craft->cur_y + craft->arms_list[i]->offset_y);
  23.     }
  24. }
  25. // 在每一次刷新时移动所有子弹
  26. void move_bullet(dfo_t *bullet)
  27. {
  28.     if (bullet->cur_life <= 0)
  29.         return;
  30.     map_t *cur_map = get_cur_map(bullet);
  31.     bullet->cur_y -= bullet->speed;
  32.     int bottom = bullet->cur_y + cur_map->offset_y + cur_map->icon->width;
  33.     if (bottom < 0 || (bullet->start_y - bullet->cur_y) > bullet->range)
  34.     {
  35.         bullet->cur_life = 0;   // 对超出射程的子弹 取消激活
  36.         bullet->cur_x = -100;
  37.     }
  38. }

 

5.8、撞击判定

在这一步,我们将会实现对于所有对象的撞击判定,并对对象的属性做出对应的处理。简单而言,撞击判定只需要检查两个对象是否有像素点的重叠即可。


  
  1. // 判断两个dfo对象 bullet craft 是否发生撞击
  2. int hit_check(dfo_t *bullet, dfo_t *craft)
  3. {
  4.     if (craft->cur_y <= 0 || craft->cur_x <= 0)
  5.         return 0;
  6.     if (craft->cur_life <= 0)
  7.         return 0;
  8.     if (bullet->cur_life <= 0)
  9.         return 0;
  10.     act_seq_t *cur_act_seq = bullet->act_seq_list[bullet->act_seq_type];
  11.     map_t *cur_map = cur_act_seq->act_seq_maps[cur_act_seq->act_seq_index];
  12.     for ( int bullet_bit_x = 0; bullet_bit_x < (cur_map->icon->height); bullet_bit_x++)
  13.     {
  14.         for ( int bullet_bit_y = 0; bullet_bit_y < (cur_map->icon->width); bullet_bit_y++)
  15.         {
  16.             uint8_t bit = (cur_map->icon->p_icon_mask == NULL) ? cur_map->icon->p_icon_data[bullet_bit_x / 8 + bullet_bit_y] & ( 0x01 << bullet_bit_x % 8) : cur_map->icon->p_icon_mask[bullet_bit_x / 8 + bullet_bit_y] & ( 0x01 << bullet_bit_x % 8);
  17.             if (bit == 0)
  18.                 continue;
  19.             int bit_cur_x = bullet->cur_x + cur_map->offset_x + cur_map->icon->height - bullet_bit_x;
  20.             int bit_cur_y = bullet->cur_y + cur_map->offset_y + bullet_bit_y;
  21.             act_seq_t *cur_craft_act_seq = craft->act_seq_list[craft->act_seq_type];
  22.             map_t *cur_craft_map = cur_craft_act_seq->act_seq_maps[cur_craft_act_seq->act_seq_index];
  23.              for ( int craft_bit_x = 0; craft_bit_x < (cur_craft_map->icon->height); craft_bit_x++)
  24.             {
  25.                 for ( int craft_bit_y = 0; craft_bit_y < (cur_craft_map->icon->width); craft_bit_y++)
  26.                 {
  27.                     uint8_t craft_bit = (cur_craft_map->icon->p_icon_mask == NULL) ? cur_craft_map->icon->p_icon_data[craft_bit_x / 8 + craft_bit_y] & ( 0x01 << craft_bit_x % 8) : cur_craft_map->icon->p_icon_mask[craft_bit_x / 8 + craft_bit_y] & ( 0x01 << craft_bit_x % 8);
  28.                     if (craft_bit == 0)
  29.                         continue;
  30.                     // 找到有效点对应的绝对坐标
  31.                     int craft_bit_cur_x = craft->cur_x + cur_craft_map->offset_x + cur_craft_map->icon->height - craft_bit_x;
  32.                     int craft_bit_cur_y = craft->cur_y + cur_craft_map->offset_y + craft_bit_y;
  33.                     // 开始遍历所有可撞击对象
  34.                     if (craft_bit_cur_x == bit_cur_x && craft_bit_cur_y == bit_cur_y)
  35.                     {
  36.                          return 1;
  37.                     }
  38.                 }
  39.             }
  40.         }
  41.     }
  42.     return 0;
  43. }

全局撞击判定,判断地图上所有存活对象的撞击情况。


  
  1. void global_hit_check( void)
  2. {
  3.     // 子弹撞击检测
  4.     for ( int j = 0; j < MAX_BULLET; j++)
  5.     {
  6.         dfo_t *bullet = bullet_group[j];
  7.         if (bullet->cur_life <= 0)
  8.             continue;
  9.         for ( int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++)
  10.         {
  11.             dfo_t *craft = enemy_crafts[i];
  12.             if (craft->cur_life <= 0)
  13.                 continue;
  14.             if (hit_check(bullet, craft))
  15.             {
  16.                 craft->cur_life -= bullet->damage;
  17.                 bullet->cur_life = 0;
  18.                 bullet->cur_x = -100;
  19.                 if (craft->cur_life <= 0)
  20.                 {
  21.                     destory(craft);
  22.                 }
  23.                 continue;
  24.             }
  25.         }
  26.     }
  27.     // 我方飞机撞击检测
  28.     for ( int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++)
  29.     {
  30.         dfo_t *craft = enemy_crafts[i];
  31.         if (craft->cur_life <= 0)
  32.             continue;
  33.         if (hit_check(my_craft, craft))
  34.         {
  35.             craft->cur_life -= my_craft->damage;
  36.             my_craft->cur_life -= craft->damage;
  37.             // 如果舰船装甲损毁 则摧毁舰船 将其动画状态置为毁灭动画
  38.             if (craft->cur_life <= 0)
  39.             {
  40.                 craft->act_seq_type = 1;
  41.                 craft->cur_life = 0;
  42.             }
  43.             if (my_craft->cur_life <= 0)
  44.             {
  45.                 my_craft->act_seq_type = 1;
  46.                 my_craft->cur_life = 0;
  47.                 g_chance--;
  48.             }
  49.             continue;
  50.         }
  51.     }
  52. }

5.9、全局刷新


  
  1. void global_update(void)
  2. {
  3.     for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++)
  4.     {
  5.         craft_update_act(enemy_crafts[i]);  // 更新所有敌机贴图状态
  6.         move_enemy(enemy_crafts[i]);        // 自动移动所有敌机
  7.     }
  8.     for (int i = 0; i < MAX_BULLET; i++)
  9.     {
  10.         move_bullet(bullet_group[i]);       // 自动移动所有激活的子弹
  11.     }
  12.     craft_update_act(my_craft);             // 更新玩家舰船状态
  13.     shut_craft(my_craft);                   // 触发玩家舰船射击
  14.     global_hit_check();                     // 全局撞击判定
  15. }

 

6、实现效果

接下来请欣赏笔者的操作。

 

7、开发者技术支持

如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号

更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/


转载:https://blog.csdn.net/HaaSTech/article/details/114014602
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场