【C++】飞机大战项目记录
发布时间:2025-06-24 17:19:52 作者:北方职教升学中心 阅读量:273
源代码与图片参考自《你好编程》的飞机大战项目,这里不进行展示。
本项目是仅供学习使用的项目
飞机大战项目记录
- 飞机大战设计报告
- 1 项目框架分析
- 1.1 敌机设计:
- 1.2 玩家飞机控制:
- 1.3 子弹发射:
- 1.4 游戏界面与互动:
- 1.5 游戏逻辑:
- 2 开始打造项目
- 2.1 图片素材准备
- 2.2 设计精灵对象
- 位置坐标:
- 大小宽度:
- draw方法:
- update方法:
- 执行机制:
- 代码
- 2.3 设计英雄飞机
- 结构体设计
- 初始化
- 绘制与更新
- 资源回收
- 关键技术点
- 代码(只展示头文件)
- 2.4 游戏场景设置
- 游戏背景
- 游戏循环函数
- 代码
- 2.5 设计子弹与敌机
- 子弹设计模块
- 结构体设计
- 初始化
- 绘制与更新
- 资源回收
- 关键技术点
- 代码(只展示头文件)
- 敌机设计模块
- 结构体设计
- 初始化
- 绘制与更新
- 交互操作
- 资源回收
- 代码(只展示头文件)
- 敌机实例化
- 2.6 设计击毁与碰撞逻辑
- 子弹与敌机碰撞检测
- 英雄飞机与敌机碰撞检测
- 关键技术点
- 2.7 计分板
- 2.8 游戏菜单
- 结构体设计
- 功能方法
- 交互逻辑
- 关键技术点
- 2.9 音乐设计
- 背景音乐
- 击毁音效
- 结构体设计
- 功能方法
- 关键技术点
- 项目效果展示
- Thanks♪(・ω・)ノ谢谢阅读!!!
- 下一篇文章见!!!
飞机大战设计报告
源代码与图片参考自《你好编程》
1 项目框架分析
根据飞机大战的游戏特性,首先可以确定的是游戏的基本玩法和规则。对于本软件项目,游戏的核心机制是使用鼠标控制一架飞机在屏幕上移动,同时飞机会自动发射子弹来击败敌机。我们将通过Easyx来实现该项目!
1.1 敌机设计:
设计三种不同类型的敌机,每种敌机具有不同的生命值和外观。
小型敌机:生命值低,移动速度快,外观较小。
中型敌机:生命值和大小适中,速度适中。
大型敌机:生命值高,移动速度慢,外观较大。
1.2 玩家飞机控制:
使用鼠标控制飞机的上下左右移动,飞机的位置随鼠标位置变化。
飞机在屏幕中任意移动,给与玩家充足飞行体验,提高游戏沉浸感。
1.3 子弹发射:
飞机自动连续射击,子弹直线向上移动。
可以设计不同的子弹类型或升级系统,提高游戏的可玩性和策略性。
1.4 游戏界面与互动:
设计一个直观的用户界面,进入游戏可以见到排行榜与开始游戏。开始游戏后,屏幕显示当前得分、生命值。
敌机被击中或击毁时有相应的动画和音效,增强游戏体验。
1.5 游戏逻辑:
敌机从屏幕顶部随机位置出现,向下移动。
玩家需要避免敌机的攻击,同时尽可能多地击落敌机。
2 开始打造项目
有了大致的游戏设计思路,现在我们可以来逐步实现飞机大战的各个模块。
2.1 图片素材准备
一个好的项目离不开美观的图案,所以这里我准备了一下图片(放在项目代码的路径下):
子弹(对应图片和图片掩码)
敌机有三种,都有对应正常飞行状态的图片,以及爆炸销毁的图片组,而且都有对应的掩码,保证图片的边框。
英雄飞机正常飞行状态有两种,模拟飞行中喷射火焰前进,以及爆炸销毁的图片组。
菜单图片与背景图片
2.2 设计精灵对象
精灵对象是游戏开发中一个常见的概念,通常用于表示屏幕上的各种动态元素。飞机大战项目中,精灵对象可以被用来作为基类,敌机和飞机都会继承这个基类。以下是精灵对象的一些基本特征和功能:
位置坐标:
每个精灵对象都有自己的位置坐标,通常包括x和y坐标,用于确定对象在游戏界面上的位置。
大小宽度:
对象的大小通常由宽度和高度来定义,这决定了精灵在屏幕上的占用空间和碰撞检测的范围。
draw方法:
这个方法负责将精灵绘制到游戏窗口。通常,这包括调用图形库(如Pygame的blit方法)来在正确的位置和尺寸绘制精灵的图像。
update方法:
update方法用于更新精灵的状态。这可能包括移动位置、改变速度、检测碰撞、更新生命值等。这个方法每一帧都会被调用,以保持游戏逻辑的持续运行和响应。
执行机制:
在游戏的主循环中,每一帧都会对所有精灵对象执行draw和update方法。update方法首先运行,以处理逻辑和状态的变更,然后是draw方法,以反映这些更新在屏幕上。
通过继承精灵对象,敌机和玩家的飞机可以复用大量的代码,使得管理游戏中的各种对象更加方便和高效。每个对象都能独立地更新自己的状态并在屏幕上表现出来,而无需每个对象单独编写大量重复的代码。这样的设计也方便了后续的扩展和维护。
代码
//每个精灵对象的脚步//通过继承来实现使用structsprite{public://绘制画面 每帧执行void(*draw)(structsprite*);//更新数据 每帧执行void(*update)(structsprite*);//坐标intx;inty;//尺寸intwidth;intheight;};
2.3 设计英雄飞机
结构体设计
plane 结构体继承自 sprite 类,包含以下元素:IMAGE* imgArrPlane[6]
:存储飞机图像。IMAGE* imgArrPlaneMask[6]
:存储飞机图像的掩码,用于在游戏中处理透明和重叠部分。enum planeStatus status
:表示飞机当前的状态(正常飞行,摧毁爆炸等)。int life
:飞机的生命值。int planeUpdateCnt
:更新计数器,用于控制状态更新频率。bool wasHit
:标记飞机是否被击中。
初始化
planeInit
函数负责初始化飞机对象:
设定绘制(draw)和更新(update)方法指向对应的函数。
初始状态设置为 normal0
,生命值为预设常量。
初始化飞机的位置坐标。
加载飞机状态对应的图像及其掩码。
绘制与更新
planeDraw
函数控制飞机在屏幕上的绘制,根据当前状态选择对应的图像和掩码。planeUpdate
函数每帧调用一次,处理飞机的状态转换:
- 如果飞机生命值大于零,交替在正常状态之间切换以模拟飞行动画。
- 如果飞机生命值为零,按序播放被击落动画直至完全摧毁。
资源回收
planeDestroy
函数清理所有动态分配的资源,防止内存泄漏。
关键技术点
- 状态管理:通过枚举管理飞机的不同状态,使得状态转换清晰易管理。
- 动态资源管理:使用动态分配的图像资源,并在对象销毁时释放,确保资源使用的正确性。
- 帧更新控制:通过 planeUpdateCnt 控制状态更新的频率,优化动画表现。
该模块充分展示了面向对象设计的优势,通过继承和多态简化了代码的复杂性,同时提高了代码的可维护性和扩展性。
代码(只展示头文件)
#pragmaonce#include<easyx.h>#include"sprite.h"#include<cstdio>//枚举类型 进行1 - 7 形态转化//其中一一对应一张照片typedefenumplaneStatus{normal0,normal1,down0,down1,down2,down3,destroy}planeStatus;//记录从正常到销毁的各个状态的顺序structplane{structspritesuper;IMAGE*imgArrPlane[6];IMAGE*imgArrPlaneMask[6];//记录目前飞机状态enumplaneStatusstatus;//记录英雄生命值intlife;//更新计数器intplaneUpdateCnt;boolwasHit;};voidplaneInit(structplane*h);voidplaneDestroy(structplane*h);voidplaneDraw(structplane*h);voidplaneUpdate(structplane*h);
2.4 游戏场景设置
游戏背景
游戏的背景是一张图片,为了模拟飞行的向前移动,可以将两张图片进行一个拼接,不断移动该合成图片。
每次图片移动一个像素,如果超出范围,那么对图片进行复位
#include"sprite.h"#include<easyx.h>structbackground{structspritesuper;//两张图片的y坐标//因为只需要前后移动 intyA;intyB;IMAGE*imgBackground;};//初始化背景voidbackgroundInit(structbackground*);//销毁背景voidbackgroundDestory(structbackground*);
游戏循环函数
在这个循环里,我们可以每一帧对需要渲染的对象进行绘制与更新,做到画面的实时更新。其中游戏场景中有许多共性,我们可以提取出来作为一个精灵对象。
#include<easyx.h>//场景基础对象//提供继承方法structscene{//四类方法//绘制场景中的所有精灵void(*draw)(structscene*);//用于更新场景中的所有精灵void(*update)(structscene*);//获取鼠标 或 键盘消息//进而控制场景中的精灵void(*control)(structscene*,ExMessage*msg);//指示该场景是否结束bool(*isQuit)(structscene*);};
- 初始化中我们对英雄飞机,敌机,游戏背景进行初始化(通过各自的初始化函数)。
- 销毁函数中依次调用各个对象的销毁函数即可。
- draw方法中 通过vector中记录的结构体指针来调用每个对象的draw方法,完成绘制任务。
- update方法中通过vector中记录的结构体指针来调用每个对象的update方法,完成更新任务。
- control方法中获取鼠标信息,检测是否移动,然后更新英雄飞机位置。
- isQuit方法检查是否需要退出。
代码
#include<easyx.h>#include<iostream>#include"sprite.h"#include<Windows.h>#include"gameloop.h"#include<conio.h>//精灵对象指针 和 帧率voidgameloop(structscene*s,intfps){//设置精度为1毫秒 画面更流畅timeBeginPeriod(1);//设置 开始时间、结束时间、频率FLARGE_INTEGER startCount,endCount,F;// 获取频率FQueryPerformanceFrequency(&F);BeginBatchDraw();//检测暂停while(1){//空格暂停boolisPaused =false;if(GetAsyncKeyState(VK_SPACE)&0x8000){// 切换暂停状态isPaused =!isPaused;// 防止连续触发while(GetAsyncKeyState(VK_SPACE)&0x8000){Sleep(10);}}if(!isPaused){//计算时间// 开始无限循环,这是游戏循环的核心部分。//记录当前的性能计数器值到startCount中,这代表了这一帧开始的时间点。QueryPerformanceCounter(&startCount);//清空画面cleardevice();//调用场景对象的draw绘制画面s->draw(s);//调用场景对象的update方法更新画面s->update(s);// 每帧会更新状态//如果失败 ,退出游戏循环if(s->isQuit(s))break;//再次调用QueryPerformanceCounter获取当前性能计数器值//并存入endCount中,QueryPerformanceCounter(&endCount);//设置流逝时间longlongelapse =(endCount.QuadPart -startCount.QuadPart)/F.QuadPart *1000000;//利用帧率计算每一帧时差while(elapse <1000000/fps){Sleep(1);ExMessage msg;//创建一个信息对象 用来接收鼠标信息boolisOK =peekmessage(&msg,EX_MOUSE);//如果有信息,进行control操作if(isOK ==true){s->control(s,&msg);}QueryPerformanceCounter(&endCount);// 更新时差elapse =(endCount.QuadPart -startCount.QuadPart)*1000000/F.QuadPart;}FlushBatchDraw();timeEndPeriod(1);}else{intcountdown =5;// 游戏暂停时的逻辑while(isPaused &&countdown >0){// 清除屏幕//cleardevice();// 绘制倒计时TCHAR s[20];setfillcolor(0xAAAAAA);solidrectangle(80,300,380,345);_stprintf_s(s,_T("暂停中... %d s"),countdown);outtextxy(100,300,s);//422, 750FlushBatchDraw();// 等待一秒Sleep(1000);// 更新倒计时countdown--;// 如果倒计时结束,自动恢复游戏if(countdown <=0){isPaused =false;}}}}}
2.5 设计子弹与敌机
子弹设计模块
结构体设计
bullet
结构体继承自 sprite 类,包含以下元素:IMAGE* imgBullet
:子弹的图像。IMAGE* imgBulletMask
:子弹图像的掩码,用于在游戏中处理透明和重叠部分。
初始化
bulletInit
函数负责初始化子弹对象:
设定绘制(draw)和更新(update)方法指向对应的函数。
载入子弹的图像和掩码,准备用于绘制。
绘制与更新
bulletDraw
函数控制子弹在屏幕上的绘制,使用子弹的图像和掩码。bulletUpdate
函数每帧调用一次,处理子弹的移动逻辑:
子弹向上移动,移动速度通过常量 bulletSpeed 控制。
资源回收
bulletDestroy
函数清理所有动态分配的资源,防止内存泄漏。
关键技术点
- 图像处理:通过使用掩码图像,子弹的绘制可以适应各种背景,使得子弹与游戏环境的融合更自然。
- 性能优化:子弹的更新逻辑简单(单一的向上移动),这有助于在屏幕上同时处理大量子弹时保持游戏性能。
- 资源管理:使用动态分配的图像资源,并在对象销毁时释放,确保资源使用的正确性。
代码(只展示头文件)
#include<easyx.h>#include"sprite.h"//子弹对象structbullet{//依旧继承sprite对象//draw update structspritesuper;IMAGE*imgBullet;//子弹图片IMAGE*imgBulletMask;//掩码图片};voidbulletInit(structbullet*);voidbulletDestroy(structbullet*);
敌机设计模块
这里我创建了三种敌机,使用枚举变量分别用0 , 1 ,2 来表示。每种敌机都有对应的状态枚举变量。我们可以提取出共性来创建一个敌机精灵对象。所有敌机均继承与这个敌机对象。
结构体设计
enemy
结构体继承自 sprite 类,包含以下字段:IMAGE** imgArrEnemy
和 IMAGE** imgArrEnemyMask
:数组,存储敌机的图像和掩码,用于不同状态下的绘制。enum enemyType enemyType
:敌机种类,定义敌机的基本属性如大小和生命值。double v
:敌机的移动速度。int life
:敌机的生命值。int enemyDownCnt
:敌机爆炸状态的计数器,用于控制爆炸动画的播放速度。int status
:当前敌机的状态,从正常飞行到被击落的不同阶段。int lastStatusBeforeDestroy
:记录销毁前的最后一个状态,用于动画过渡。
初始化
enemyInit
函数负责初始化敌机对象:
设置绘制(draw)和更新(update)方法指向对应的函数。
初始化敌机的状态为 enemy_normal
。
设置敌机的随机移动速度。
加载敌机状态对应的图像及其掩码。
绘制与更新
enemyDraw
函数控制敌机在屏幕上的绘制,使用敌机的当前状态对应的图像和掩码。enemyUpdate
函数每帧调用一次,处理敌机的移动和状态转换:
敌机向下移动,速度由 v 控制(随机值控制)。
当生命值为零时,敌机进入爆炸状态,逐渐播放爆炸动画直到完全摧毁。
交互操作
enemyHited
函数处理敌机被子弹击中的情况:
生命值递减。
生命值为零时开始播放爆炸动画。
资源回收
destro
y 方法(未提供完整实现)应负责清理动态分配的图像资源,防止内存泄漏。
代码(只展示头文件)
#pragmaonce#include<easyx.h>#include"sprite.h"//该文件为敌机的共性文件//每种代表一种敌机enumenemyType{enemyType0,//小enemyType1,//中enemyType2 //大};//中 小敌机 五张图片//大敌机7张enumenemyStatus{enemy_normal,//正常状态enemy_down0,enemy_down1,enemy_down2,enemy_down3,enemy_down4,enemy_down5,enemy_destroy};structenemy{structspritesuper;//敌机被击中处理void(*hited)(structenemy*);//销毁敌机void(*destroy)(structenemy*);//敌机图片和掩码图片IMAGE**imgArrEnemy;IMAGE**imgArrEnemyMask;//敌机种类enumenemyTypeenemyType;doublev;// 移动速度intlife;//生命值intenemyDownCnt;//爆炸状态计数器intstatus;//敌机状态intlastStatusBeforeDestroy;//销毁前最后一个状态};voidenemyInit(structenemy*e);voidenemyDraw(structenemy*e);
敌机实例化
通过上面的敌机共性,我们就可以绘制产生三种不同的敌机,同过对其中元素的修改就可以完成对应的功能。加载对应图片,绘制到相应位置。然后将敌机的绘制更新方法移动到mainscene的绘制更新中。
我们需要一个vector容器来容纳敌机。
敌机的产生逻辑是:通过随机数来确定产生那一种敌机(可以调整概率来改变敌机出现的种类数量),然后调用对应的初始化化函数,并储 在对应vector容器中
敌机的销毁逻辑是:判断是否出界和判断是否被子弹击中。每次检查直接遍历容器中所有的敌机,移出应该被销毁的敌机并回收对应资源。
2.6 设计击毁与碰撞逻辑
预期情况下,子弹击中敌机,敌机应该被销毁,英雄飞机撞击到敌机,英雄飞机应该被销毁。
为了检查子弹是否击中敌机,我们增添一个bulletEnemyCheck函数。
子弹与敌机碰撞检测
bulletHitEnemyCheck
函数遍历所有子弹和敌机,检查每颗子弹是否与敌机的碰撞框发生重叠。
子弹抽象为其头部的一个点进行精确检测。
如果子弹的位置在敌机的矩形区域内,触发敌机的 hited 函数,处理击中逻辑(生命值减少,状态改变)。
如果击中敌机,子弹会被销毁,同时移除子弹列表中的该子弹项,防止重复检测。
敌机生命值减为零时,触发播放击毁音效。
//检测子弹是否撞击voidbulletHitEnemyCheck(structmainScene*s){// 遍历所有子弹碰撞检测for(inti =0;i <s->vecBullets.size;i++){// 将子弹抽象为头部的一个点structbullet*pBullet =(structbullet*)s->vecBullets.get(&s->vecBullets,i);POINT bulletPoint;bulletPoint.x =pBullet->super.x +6/2;//头部中心点bulletPoint.y =pBullet->super.y;// 检查每一颗子弹是否碰撞到任意敌机for(intj =0;j <s->vecEnemy.size;j++){structenemy*pEnemy =(structenemy*)s->vecEnemy.get(&s->vecEnemy,j);// 敌机的宽度与高度intwidth,height;width =pEnemy->super.width;height =pEnemy->super.height;// 敌机矩形区域intleft,top,right,bottom;left =pEnemy->super.x;top =pEnemy->super.y;right =left +width;bottom =top +height;// 检查子弹是否在敌机矩形区域内if(bulletPoint.x >left &&bulletPoint.x <right &&bulletPoint.y >top &&bulletPoint.y <bottom ){if(pEnemy->life !=0){// 子弹撞击到敌机后,销毁子弹bulletDestroy(pBullet);free(pBullet);s->vecBullets.remove(&s->vecBullets,i);i--;// 敌机击中pEnemy->hited(pEnemy);//播放击毁音效if(pEnemy->life ==0){s->enemyDownSoundMgr.play(&s->enemyDownSoundMgr);}break;}}}}}
英雄飞机与敌机碰撞检测
heroHitEnemyCheck
函数检查主角飞机与每个敌机是否发生重叠。
使用飞机和敌机的矩形碰撞框进行碰撞检测。只有当飞机处于正常飞行状态时,才进行碰撞检测。
如果检测到重叠,返回真值表示飞机受到攻击。
boolheroHitEnemyCheck(structmainScene*s){// plane矩形区域(比飞机实际区域较小)RECT rectHero;rectHero.left =s->plane->super.x +16;rectHero.top =s->plane->super.y +10;rectHero.right =s->plane->super.x +16*3;rectHero.bottom =s->plane->super.y +62;for(inti =0;i <s->vecEnemy.size;i++){structenemy*pEnemy =(structenemy*)s->vecEnemy.get(&s->vecEnemy,i);intenemyWidth =0,enemyHeight =0;if(pEnemy->status !=enemy_normal)continue;// 敌机矩形区域RECT rectEnemy;rectEnemy.left =pEnemy->super.x;rectEnemy.top =pEnemy->super.y;rectEnemy.right =pEnemy->super.x +pEnemy->super.width;rectEnemy.bottom =pEnemy->super.y +pEnemy->super.height;// 两区域是否重叠if(rectHero.left <=rectEnemy.right &&rectHero.right >=rectEnemy.left &&rectHero.top <=rectEnemy.bottom &&rectHero.bottom >=rectEnemy.top){if(s->plane->status ==normal0 ||s->plane->status ==normal1)returntrue;}}returnfalse;}
关键技术点
- 碰撞准确性:通过精确定义子弹的头部位置和飞机与敌机的具体矩形区域,提高碰撞检测的准确性。
- 资源管理:在检测到碰撞时,及时销毁子弹并从列表中移除,优化内存使用和计算性能。
- 游戏互动性增强:碰撞检测是增强游戏互动性的关键组成部分,使得游戏过程充满挑战性和反应需求。
2.7 计分板
计分的环节很简单:
- 小敌机 - 10
- 中敌机 - 20
- 大敌机 - 50
击毁敌机后 进行一个分数的叠加(mainscene中有对应mark变量来记录分数)即可
计分版的绘制也要加入到mainscene中的绘制里,每帧都进行更新。
//打印分数charbuff[30];sprintf(buff,"得分:%d 生命值:%d",s->mark,s->plane->life );outtextxy(0,0,buff);
2.8 游戏菜单
结构体设计
menuScene
结构体继承自 scene 类
,增加了特定的功能和属性来处理菜单操作:IMAGE* bk
:背景图片。RECT rectStartGame, rectEndGame
:开始游戏和结束游戏按钮的矩形区域。bool isStartGameHover, isEndGameHover
:标记鼠标是否悬停在对应的按钮上。bool isQuit
:标记是否退出菜单场景。
功能方法
menuSceneInit
:初始化菜单场景,设置按钮的位置和大小,加载背景图像。menuSceneDraw
:绘制菜单背景和按钮。根据鼠标是否悬停在按钮上改变按钮文字颜色。menuSceneUpdate
:一个空函数,因为菜单界面可能不需要在每帧都更新数据。menuSceneControl
:处理菜单的交互逻辑,包括鼠标移动和点击事件:
如果鼠标悬停或离开按钮区域,更新悬停状态。
点击开始游戏按钮时,设置退出标志。
点击排行榜按钮时,读取并显示排行榜信息(打印到控制台)。menuSceneIsQuit
:返回是否退出菜单的状态。
交互逻辑
根据用户的输入(鼠标移动和点击),更新界面显示和状态。这包括悬停效果和响应按钮点击。
关键技术点
事件驱动:菜单的交互完全基于事件,如鼠标移动和点击,允许响应式更新。
图形用户界面(GUI)管理:使用矩形框来管理按钮的位置和大小,易于调整和管理。
资源管理:加载并显示图像,以及在适当时机销毁资源,防止内存泄漏。
2.9 音乐设计
背景音乐
音乐设计是比较简单的,我们在mainscene
调用音乐文件即可:
//敌机音乐初始化soundManagerInit(&s->enemyDownSoundMgr,"sounds/enemy_down.wma");mciSendString("open sounds/background.mp3",NULL,0,NULL);mciSendString("play sounds/background.mp3 repeat",NULL,0,NULL);
退出后要在mainSceneDestroy
关闭音乐文件
// 停止背景音乐mciSendString("close sounds/background.wma",NULL,0,NULL);// 停止英雄爆炸音乐mciSendString("close sounds/hero_down.wma",NULL,0,NULL);// 停止敌机爆炸音效soundManagerDestroy(&s->enemyDownSoundMgr);
击毁音效
检测到碰撞就进行播放,每个击毁声音j结构体使用vector容器进行储存,使其可以同步播放。
结构体设计
soundManager
结构体包括以下主要成员:vector vecSoundAlias
:存储音频别名的向量,用于跟踪和管理多个音频实例。char soundPath[100]
:存储音频文件的路径。
函数指针 play
和 close
:分别用于播放音频和关闭音频。
功能方法
soundPlay
:启动音频播放。使用 mciSendString
函数根据音频路径和动态生成的别名来打开和播放音频。soundClose
:根据指定的时间间隔检查并关闭已完成播放的音频实例。这通过比较当前时间和音频开始播放的时间来决定是否关闭音频。soundManagerInit
:初始化音频管理器,设置路径和函数指针,并初始化音频别名向量。soundManagerDestroy
:销毁音频管理器,关闭所有音频实例并释放资源。
关键技术点
- 动态资源管理:通过动态分配的别名来管理音频资源,确保每个音频实例都可以独立控制和释放。
- 时间驱动的资源释放:使用系统的当前时间来判断音频是否播放完毕,并根据结果关闭音频实例,有效管理内存和系统资源。
- 复杂的音频处理:允许同时处理多个音频播放,提高游戏的多任务处理能力和用户体验。
项目效果展示
通过上面的设计,我们实现来看飞机大战的主要功能