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