1@page aircraftBattle 飞机大战 2# 实验介绍 3 飞机大战作为一款经典的街机游戏,是很多人的童年回忆。我们的 HaaS EDU K1 开发板专门设计了街机样式的按键排列,很适合我们做这类游戏的开发。 4 5 6<div align=center> 7 <img src="https://img.alicdn.com/imgextra/i2/O1CN01fFHJ801HyoDT19NYi_!!6000000000827-1-tps-1200-800.gif" style="zoom:50%;" /> 8</div> 9 10# 涉及知识点 11 OLED绘图 12 按键事件 13 14# 开发环境准备 15## 硬件 16 开发用电脑一台 17 HAAS EDU K1 开发板一块 18 USB2TypeC 数据线一根 19 20## 软件 21### AliOS Things开发环境搭建 22 开发环境的搭建请参考 @ref HaaS_EDU_K1_Quick_Start (搭建开发环境章节),其中详细的介绍了AliOS Things 3.3的IDE集成开发环境的搭建流程。 23 24### HaaS EDU K1 DEMO 代码下载 25 HaaS EDU K1 DEMO 的代码下载请参考 @ref HaaS_EDU_K1_Quick_Start (创建工程章节),其中, 26 选择解决方案: 基于教育开发板的示例 27 选择开发板: haaseduk1 board configure 28 29### 代码编译、烧录 30 参考 @ref HaaS_EDU_K1_Quick_Start (3.1 编译工程章节),点击 ✅ 即可完成编译固件。 31 参考 @ref HaaS_EDU_K1_Quick_Start (3.2 烧录镜像章节),点击 "⚡️" 即可完成烧录固件。 32 33 34# 游戏设定 35 不同于规则简单的贪吃蛇,在飞机大战这类游戏中,往往需要对游戏中出现的每个对象进行数值、行为的设定。在开发游戏前期,梳理好这些设定也有助于我们更清晰地进行开发。有时,优秀的设定也是吸引玩家的重要因素。 36## 角色设定 37 38<div align=center> 39 <img src="https://img.alicdn.com/imgextra/i4/O1CN01d8HY3L1eJnv3GDqni_!!6000000003851-2-tps-451-241.png" /> 40</div> 41 42 43<div align=center> 44 <img src="https://img.alicdn.com/imgextra/i3/O1CN017l0kl91MtpzerUl1V_!!6000000001493-2-tps-451-206.png" /> 45</div> 46 47 48<div align=center> 49 <img src="https://img.alicdn.com/imgextra/i4/O1CN01Bh1oSu1MWTnP9XmpP_!!6000000001442-2-tps-450-206.png" /> 50</div> 51 52 53<div align=center> 54 <img src="https://img.alicdn.com/imgextra/i4/O1CN01vX0n0x20SQvdUNLJs_!!6000000006848-2-tps-450-205.png" /> 55</div> 56 57 58## 行为设定 59 60 - 在本游戏中,玩家将控制阿克琉斯级战舰,在持续不断的敌机中通过闪避或攻击开辟出自己的路。 61 - 玩家可以通过 HaaS EDU K1 的四个按键控制 阿克琉斯级战舰 进行前后左右运动。 62 - 在游戏进行过程中,玩家的战舰会不断发射炮弹。被炮弹攻击的敌方战舰会损失响应的装甲。 63 - 若玩家战舰被敌方战舰撞击,双方均会损失装甲。 64 - 玩家有三次紧急修复战舰的机会。 65# 游戏实现 66## 游戏流程 67 在开始之前,我们先使用一个简单的流程,帮助大家理解本游戏的刷新机制。这个大循化即游戏刷新所需要的所有流程。 68```c 69// 游戏中所有对象的更新判定由大循环维护 70void aircraftBattle_task() 71{ 72 while (1) 73 { 74 OLED_Clear(); // 清理屏幕数据 75 global_update(); // 刷新全局对象 如更新对象的贴图状态 发射子弹 撞击判断 等 76 global_draw(); // 绘制刷新完后的所有对象 77 OLED_Refresh_GRAM();// 将绘制结果显示在屏幕上 78 aos_msleep(40); // 40ms 为一个游戏周期 79 } 80} 81``` 82 83<div align=center> 84 <img src="https://img.alicdn.com/imgextra/i2/O1CN018hWRfi1QiAMoH4hVJ_!!6000000002009-2-tps-579-394.png" /> 85</div> 86 87 88## 贴图实现 89 对于每个对象,我们希望能够将其定位到游戏地图上的每一点,而不是单纯使用贴图函数。因此,每个对象有一个“控制坐标”,而我们相对这个“控制坐标”计算出贴图坐标。这样,如果一个对象需要变换不同尺寸的贴图,我们可以更方便地计算出它的贴图坐标。 90 如图,红色为该对象的控制坐标,蓝色为该贴图的贴图坐标。 91 92 93<div align=center> 94 <img src="https://img.alicdn.com/imgextra/i2/O1CN01tnIUT71Jy0l22oM7d_!!6000000001096-2-tps-384-384.png" style="zoom:50%;" /> 95</div> 96 97 98<div align=center> 99 <img src="https://img.alicdn.com/imgextra/i2/O1CN01Zm97E01G9DzcNmDtv_!!6000000000579-2-tps-384-324.png" style="zoom:50%;" /> 100</div> 101 102```c 103typedef struct 104{ 105 map_t *map; // 贴图 106 int cur_x; 107 int cur_y; // 飞行物对象的控制坐标 108} dfo_t; // 飞行物对象 109``` 110```c 111/* 112 -> x 113 ____________________ 114 | | icon| 115 | | of_y | 116 \/ | | | 117 y |--of_x--cp | 118 |__________________| 119*/ 120 121typedef struct 122{ 123 icon_t *icon; // 贴图对象 124 int offset_x; 125 int offset_y; // 相对于控制坐标的偏移 126} map_t; // 贴图 127``` 128 注意⚠️,在开发过程中,我们使用的是竖屏模式,坐标系是以竖屏做处理。因此,在绘图时,我们需要做坐标系的转换。 129 130 131<div align=center> 132 <img src="https://img.alicdn.com/imgextra/i2/O1CN01GOOOKe25dwxtslX50_!!6000000007550-2-tps-953-491.png" /> 133</div> 134 135 136```c 137void draw_dfo(dfo_t *dfo) 138{ 139 map_t *cur_map = get_cur_map(dfo); // 获取当前对象的贴图 140 141 // 计算对象边界 142 int top = dfo->cur_y + cur_map->offset_y; 143 int bottom = dfo->cur_y + cur_map->offset_y + cur_map->icon->width; 144 int left = dfo->cur_x + cur_map->offset_x; 145 int right = dfo->cur_x + cur_map->offset_x + cur_map->icon->height; 146 147 // 若对象超出屏幕 则不绘制 148 if (top > 132 || bottom < 0 || left > 64 || right < 0) 149 return; 150 151 // 绘制坐标转换后的贴图对象 152 OLED_Icon_Draw( 153 dfo->cur_y + cur_map->offset_y, 154 64 - (dfo->cur_x + cur_map->offset_x + cur_map->icon->height), 155 cur_map->icon, 156 2); 157} 158``` 159 这样,就可以实现在OLED上绘制我们设定的战舰图片了。 160 161 162<div align=center> 163 <img src="https://img.alicdn.com/imgextra/i1/O1CN01EejcyI1DtzaUfeMGg_!!6000000000275-2-tps-1200-800.png" style="zoom:50%;" /> 164</div> 165 166 167## 移动战舰 168 接下来,我们要实现的是根据用户的按键输入来移动战舰的贴图。在此之前,我们需要对 dfo_t 结构体进行更多的补充。我们额外定义一个 speed 属性,用于定义在用户每次操作时移动一定的距离。 169 注意,这里的前后左右均是在游戏坐标系中。 170```c 171typedef struct 172{ 173 // 舰船坐标 174 int cur_x; // 运动 175 int cur_y; 176 // 舰船速度 177 uint8_t speed; // 绝对固定 178 // 舰船贴图 179 map_t *map; 180} dfo_t; // Dentified Flying Object 181``` 182```c 183typedef enum 184{ 185 UP, 186 LEFT, 187 RIGHT, 188 DOWN 189} my_craft_dir_e_t; 190 191void move_MyCraft(dfo_t *my_craft, my_craft_dir_e_t dir) 192{ 193 // 获取舰船当前的贴图对象 194 map_t *cur_map = get_cur_map(my_craft); 195 // 计算贴图边界 196 int top = my_craft->cur_y + cur_map->offset_y; 197 int bottom = my_craft->cur_y + cur_map->offset_y + cur_map->icon->width; 198 int left = my_craft->cur_x + cur_map->offset_x; 199 int right = my_craft->cur_x + cur_map->offset_x + cur_map->icon->height; 200 // 判断方向 201 switch (dir) 202 { 203 case UP: 204 // 如果这次移动不会超过地图边界 则移动 205 if (!(top - my_craft->speed < 0)) 206 my_craft->cur_y -= my_craft->speed; 207 break; 208 case DOWN: 209 if (!(bottom + my_craft->speed > 132)) 210 my_craft->cur_y += my_craft->speed; 211 break; 212 case LEFT: 213 if (!(left - my_craft->speed < 0)) 214 my_craft->cur_x -= my_craft->speed; 215 break; 216 case RIGHT: 217 if (!(right + my_craft->speed > 64)) 218 my_craft->cur_x += my_craft->speed; 219 break; 220 default: 221 break; 222 } 223} 224``` 225 将按键回调函数关联至移动舰船函数。注意,这里的前后左右均是在游戏坐标系中。 226```c 227void aircraftBattle_key_handel(key_code_t key_code) 228{ 229 switch (key_code) 230 { 231 case EDK_KEY_4: 232 move_MyCraft(my_craft, LEFT); 233 break; 234 case EDK_KEY_1: 235 move_MyCraft(my_craft, UP); 236 break; 237 case EDK_KEY_3: 238 move_MyCraft(my_craft, DOWN); 239 break; 240 case EDK_KEY_2: 241 move_MyCraft(my_craft, RIGHT); 242 break; 243 default: 244 break; 245 } 246} 247``` 248 249 250<div align=center> 251 <img src="https://img.alicdn.com/imgextra/i4/O1CN01PZPCi622CynOOUGGa_!!6000000007085-1-tps-1200-800.gif" style="zoom:50%;" /> 252</div> 253 254 255## 加一点特效 256 作为一个注重细节,精益求精的开发者,我们希望给我们的舰船加上一些特效。而这需要舰船对象不断改变重绘自己的贴图。为了这个功能,我们额外创建了一个新的结构体用于管理“动画”。 257```c 258typedef struct 259{ 260 map_t **act_seq_maps; // 贴图指针数组 该动画的所有贴图(例如爆炸动作包含3帧) 261 uint8_t act_seq_len; // 贴图指针数组长度 262 uint8_t act_seq_index; // 用于索引帧 263 264 uint8_t act_seq_interval; // 帧间延迟 265 uint8_t act_seq_interval_cnt; // 用于延迟计数 266 267 uint8_t act_is_destory; // 用于标记该动画是否是毁灭动画 若是则不再重复 268} act_seq_t; 269``` 270 同时,每个舰船对象新增了一系列属性 act_seq_type, 用于显示当前的贴图状态。例如,当 act_seq_type = 0 时,表示舰船处于正常状态,每隔 act_seq_interval 个周期切换显示一次贴图,即第一行的三帧贴图。当 act_seq_type = 1 时,表示舰船处于爆炸状态,每隔 act_seq_interval 个周期切换显示一次贴图,即第二行的三帧贴图。 271 目前 act_seq_type 的含义由每个舰船对象自己定义和维护。也可以归纳成统一的枚举量,这一步读者可以自行完成。 272 273 274<div align=center> 275 <img src="https://img.alicdn.com/imgextra/i2/O1CN01lFBMge1IN5CwY4VTI_!!6000000000880-2-tps-509-296.png" /> 276</div> 277 278 279```c 280typedef struct 281{ 282 int cur_x; 283 int cur_y; 284 uint8_t speed; 285 286 act_seq_t **act_seq_list; // 动画数组 包含了多个动作序列 287 uint8_t act_seq_list_len; // 动画数组长度 288 uint8_t act_seq_type; 289} dfo_t; 290``` 291```c 292// 正常动作序列 293act_seq_t *achilles_normal_act = (act_seq_t *)malloc(sizeof(act_seq_t)); 294achilles_normal_act->act_seq_maps = achilles_normal_maplist; 295achilles_normal_act->act_seq_len = 3; // 该动作序列包含3帧图片 296achilles_normal_act->act_seq_interval = 10; // 该动画帧间延迟10周期 297achilles_normal_act->act_is_destory = 0; // 该动画不是毁灭动画 即一直重复 298// 毁灭动作序列 299act_seq_t *achilles_destory_act = (act_seq_t *)malloc(sizeof(act_seq_t)); 300achilles_destory_act->act_seq_maps = achilles_destory_maplist; 301achilles_destory_act->act_seq_len = 3; 302achilles_destory_act->act_seq_interval = 4; // 该动画帧间延迟4周期 303achilles_destory_act->act_is_destory = 1; 304// 动作序列数组 305act_seq_t **achilles_act_seq_list = (act_seq_t **)malloc(sizeof(act_seq_t *) * achilles->act_seq_list_len); 306achilles_act_seq_list[0] = achilles_normal_act; 307achilles_act_seq_list[1] = achilles_destory_act; 308// 将舰船对象属性指向该动作序列数组 309achilles->act_seq_list = achilles_act_seq_list; 310achilles->act_seq_type = 0; 311``` 312 定义完成后,我们需要在游戏的每一次循环中,更新战舰状态和贴图。 313```c 314void craft_update_act(dfo_t *craft) 315{ 316 act_seq_t *cur_act_seq = craft->act_seq_list[craft->act_seq_type]; 317 if (cur_act_seq->act_seq_interval == 0) 318 return; // 若当前战舰无动作序列,则不进行更新 319 ++(cur_act_seq->act_seq_interval_cnt); 320 if (cur_act_seq->act_seq_interval_cnt >= cur_act_seq->act_seq_interval) 321 { 322 cur_act_seq->act_seq_interval_cnt = 0; 323 ++(cur_act_seq->act_seq_index); // 切换贴图 324 if (cur_act_seq->act_seq_index >= cur_act_seq->act_seq_len) 325 { 326 cur_act_seq->act_seq_index = 0; 327 if (cur_act_seq->act_is_destory == 1) 328 { 329 // 在这里处理毁灭的舰船 330 } 331 } 332 } 333} 334``` 335 这样,我们就为战舰添加了喷气的特效。 336 337 338<div align=center> 339 <img src="https://img.alicdn.com/imgextra/i2/O1CN01xPmTtB28sYRWTrbj6_!!6000000007988-1-tps-1200-800.gif" style="zoom:50%;" /> 340</div> 341 342 343## 移动敌机 344 移动敌机的方式更简单。只需要将其向下移动即可。实现方式如下。 345```c 346void move_enemy(dfo_t *craft) 347{ 348 map_t *cur_map = get_cur_map(craft); 349 craft->cur_y += craft->speed; 350 int top = craft->cur_y + cur_map->offset_y; 351 if (top > 132) // 当敌机飞过屏幕下方 352 reload_dfo(craft, AUTO_RELOAD, AUTO_RELOAD); // 重载敌机 353} 354``` 355## 重载敌机 356 在飞机大战中,会有持续不断的敌机生成,并且敌机的出现顺序和位置都随机。为了实现这种效果,我们采用的方式是维护一个敌机数组,当敌机飞过屏幕下方或是被击落后,我们会回收敌机并重新加载,将其重新显示在屏幕上。 357```c 358void reload_dfo(dfo_t *craft, int pos_x, int pos_y) 359{ 360 craft->cur_x = craft->pos_x; 361 craft->cur_y = craft->pos_y; 362 363 if (pos_x == AUTO_RELOAD) // 如果指定重载坐标为自动重载 364 { 365 uint16_t height = get_cur_map(craft)->icon->width; 366 craft->cur_x = random() % (64 - height) + height / 2; // 则随机生成一个坐标,且保证对象显示在地图内 367 } 368 if (pos_y == AUTO_RELOAD) 369 { 370 uint16_t width = get_cur_map(craft)->icon->height; 371 craft->cur_y = -(random() % 1000) - width / 2; 372 } 373} 374``` 375 这样,就能够实现源源不断的敌机了。 376 377 378<div align=center> 379 <img src="https://img.alicdn.com/imgextra/i2/O1CN013ttk521FYaJ1UKtgR_!!6000000000499-1-tps-1200-800.gif" style="zoom:50%;" /> 380</div> 381 382 383## 发射子弹 384 对于子弹而言,它和战舰的属性非常相似,因此我们在现有的舰船对象 dfo_t 上稍加改动即可。 385```c 386typedef enum 387{ 388 Achilles, // 阿克琉斯级 389 Venture, // 冲锋者级 390 Ares, // 阿瑞斯级 战神级 391 TiTan, // 泰坦级 392 Bullet, // 子弹 393} dfo_model_e_t; // 飞行物型号 394 395typedef struct 396{ 397 int offset_x; 398 int offset_y; // 炮台的相对位置 399} arms_t; // 武装结构体 400 401typedef struct 402{ 403 dfo_model_e_t model; // 型号 404 405 // 运动相关 406 int start_x; // 飞行物的起始位置 用于计算飞行距离 407 int start_y; 408 409 int cur_x; // 飞行物的当前位置 410 int cur_y; 411 412 uint8_t speed; // 飞行物的运动速度 413 unsigned int range; // 射程 414 415 // 显示相关 416 act_seq_t **act_seq_list; // 动画数组 417 uint8_t act_seq_list_len; // 动画数组长度 418 uint8_t act_seq_type; // 动画状态 419 420 // 攻击相关 421 arms_t **arms_list; // 武器装备数组 422 uint8_t arms_list_len; // 武器数组长度 423} dfo_t; 424``` 425 那么,目前 dfo_t 结构体不仅仅可以用于舰船,也可以用于定义子弹。接下来,我们为舰船定义炮台和子弹。 426```c 427dfo_t *create_achilles() // 定义阿克琉斯级战舰 428{ 429 // 贴图等其他定义 430 431 achilles->damage = 8; // 定义撞击伤害 432 achilles->full_life = 10; // 定义完整装甲值 433 achilles->cur_life = 10; // 初始化装甲值 434 435 achilles->arms_list_len = 2; // 设定炮台数为2 436 achilles->arms_list = achilles_arms_list; // 定义炮台数组 437 438 return achilles; 439} 440 441dfo_t *create_bullet() 442{ 443 // 贴图等其他定义 444 445 bullet->damage = 1; // 定义射击伤害 446 bullet->full_life = 1; // 定义完整装甲值 447 bullet->cur_life = 0; // 初始化子弹时 默认不激活 448 449 bullet->start_x = -100; // 初始化子弹时 将其移出屏幕外不做处理 450 bullet->start_y = -100; 451 bullet->cur_x = -100; 452 bullet->cur_y = -100; 453 454 return bullet; 455} 456``` 457 为了生成持续不断的子弹,我们也采用重载的方式去生成子弹。 458```c 459// 检索未被激活的子弹 460dfo_t *get_deactived_bullet() 461{ 462 for (int i = 0; i < MAX_BULLET; i++) 463 { 464 if (bullet_group[i]->cur_life <= 0) 465 return bullet_group[i]; 466 } 467 return NULL; 468} 469 470// 触发舰船射击子弹 471void shut_craft(dfo_t *craft) 472{ 473 if (craft->arms_list == NULL || craft->arms_list_len == 0) 474 return; 475 476 // 从每个炮台重载子弹 477 for (int i = 0; i < craft->arms_list_len; i++) 478 { 479 dfo_t *bullet = get_deactived_bullet(); 480 if (bullet == NULL) 481 return; 482 reload_dfo(bullet, craft->cur_x + craft->arms_list[i]->offset_x, craft->cur_y + craft->arms_list[i]->offset_y); 483 } 484} 485 486// 在每一次刷新时移动所有子弹 487void move_bullet(dfo_t *bullet) 488{ 489 if (bullet->cur_life <= 0) 490 return; 491 map_t *cur_map = get_cur_map(bullet); 492 bullet->cur_y -= bullet->speed; 493 int bottom = bullet->cur_y + cur_map->offset_y + cur_map->icon->width; 494 if (bottom < 0 || (bullet->start_y - bullet->cur_y) > bullet->range) 495 { 496 bullet->cur_life = 0; // 对超出射程的子弹 取消激活 497 bullet->cur_x = -100; 498 } 499} 500``` 501 502 503<div align=center> 504 <img src="https://img.alicdn.com/imgextra/i4/O1CN01zhXJZ11rzZ2IWhd9I_!!6000000005702-1-tps-1200-800.gif" style="zoom:50%;" /> 505</div> 506 507 508## 撞击判定 509 在这一步,我们将会实现对于所有对象的撞击判定,并对对象的属性做出对应的处理。简单而言,撞击判定只需要检查两个对象是否有像素点的重叠即可。 510```c 511// 判断两个dfo对象 bullet craft 是否发生撞击 512int hit_check(dfo_t *bullet, dfo_t *craft) 513{ 514 if (craft->cur_y <= 0 || craft->cur_x <= 0) 515 return 0; 516 if (craft->cur_life <= 0) 517 return 0; 518 if (bullet->cur_life <= 0) 519 return 0; 520 act_seq_t *cur_act_seq = bullet->act_seq_list[bullet->act_seq_type]; 521 map_t *cur_map = cur_act_seq->act_seq_maps[cur_act_seq->act_seq_index]; 522 523 for (int bullet_bit_x = 0; bullet_bit_x < (cur_map->icon->height); bullet_bit_x++) 524 { 525 for (int bullet_bit_y = 0; bullet_bit_y < (cur_map->icon->width); bullet_bit_y++) 526 { 527 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); 528 if (bit == 0) 529 continue; 530 531 int bit_cur_x = bullet->cur_x + cur_map->offset_x + cur_map->icon->height - bullet_bit_x; 532 int bit_cur_y = bullet->cur_y + cur_map->offset_y + bullet_bit_y; 533 534 act_seq_t *cur_craft_act_seq = craft->act_seq_list[craft->act_seq_type]; 535 map_t *cur_craft_map = cur_craft_act_seq->act_seq_maps[cur_craft_act_seq->act_seq_index]; 536 537 for (int craft_bit_x = 0; craft_bit_x < (cur_craft_map->icon->height); craft_bit_x++) 538 { 539 for (int craft_bit_y = 0; craft_bit_y < (cur_craft_map->icon->width); craft_bit_y++) 540 { 541 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); 542 if (craft_bit == 0) 543 continue; 544 // 找到有效点对应的绝对坐标 545 int craft_bit_cur_x = craft->cur_x + cur_craft_map->offset_x + cur_craft_map->icon->height - craft_bit_x; 546 int craft_bit_cur_y = craft->cur_y + cur_craft_map->offset_y + craft_bit_y; 547 // 开始遍历所有可撞击对象 548 if (craft_bit_cur_x == bit_cur_x && craft_bit_cur_y == bit_cur_y) 549 { 550 return 1; 551 } 552 } 553 } 554 } 555 } 556 return 0; 557} 558``` 559 全局撞击判定,判断地图上所有存活对象的撞击情况。 560```c 561void global_hit_check(void) 562{ 563 // 子弹撞击检测 564 for (int j = 0; j < MAX_BULLET; j++) 565 { 566 dfo_t *bullet = bullet_group[j]; 567 if (bullet->cur_life <= 0) 568 continue; 569 570 for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++) 571 { 572 dfo_t *craft = enemy_crafts[i]; 573 if (craft->cur_life <= 0) 574 continue; 575 576 if (hit_check(bullet, craft)) 577 { 578 craft->cur_life -= bullet->damage; 579 bullet->cur_life = 0; 580 bullet->cur_x = -100; 581 if (craft->cur_life <= 0) 582 { 583 destory(craft); 584 } 585 continue; 586 } 587 } 588 } 589 590 // 我方飞机撞击检测 591 for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++) 592 { 593 dfo_t *craft = enemy_crafts[i]; 594 if (craft->cur_life <= 0) 595 continue; 596 597 if (hit_check(my_craft, craft)) 598 { 599 craft->cur_life -= my_craft->damage; 600 my_craft->cur_life -= craft->damage; 601 // 如果舰船装甲损毁 则摧毁舰船 将其动画状态置为毁灭动画 602 if (craft->cur_life <= 0) 603 { 604 craft->act_seq_type = 1; 605 craft->cur_life = 0; 606 } 607 if (my_craft->cur_life <= 0) 608 { 609 my_craft->act_seq_type = 1; 610 my_craft->cur_life = 0; 611 g_chance--; 612 } 613 continue; 614 } 615 } 616} 617``` 618## 全局刷新 619```c 620void global_update(void) 621{ 622 for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++) 623 { 624 craft_update_act(enemy_crafts[i]); // 更新所有敌机贴图状态 625 move_enemy(enemy_crafts[i]); // 自动移动所有敌机 626 } 627 for (int i = 0; i < MAX_BULLET; i++) 628 { 629 move_bullet(bullet_group[i]); // 自动移动所有激活的子弹 630 } 631 craft_update_act(my_craft); // 更新玩家舰船状态 632 shut_craft(my_craft); // 触发玩家舰船射击 633 global_hit_check(); // 全局撞击判定 634} 635``` 636# 实现效果 637 接下来请欣赏笔者的操作。 638 639<div align=center> 640 <img src="https://img.alicdn.com/imgextra/i2/O1CN01PuExAK1BsV3zJrGs5_!!6000000000001-1-tps-1200-800.gif" style="zoom:50%;" /> 641</div> 642 643