【C语言游戏】微信飞机大战 | PlaneFight(EasyX,drawAlpha绘制透明贴图,计时器,计帧器,游戏难度自动调整,接受鼠标消息,源码素材免费分享)

一、数据结构介绍

struct aircraft

//所有飞机的结构体
typedef struct aircraft{
int type;//飞机类型
int HP;//剩余血量
int bomb_supply;//炸弹数量
clock_t bullet_supply;//弹药补给的开始时间
int x;
int y;
int height;
int width;
int speed;
int state;//飞机当前的状态
int statemax;//飞机的最大状态号
int cnt[4];//四个计帧器
}Aircraft;


struct ammo

//子弹和空投补给的结构体
typedef struct ammo{
int type;
bool isExist;//是否存在
int x;
int y;
int speed;
int height;
int width;
}Ammo;

//枚举类型定义各种类型,状态
在这里插入图片描述

//定义了全局结构体数组
extern Aircraft Plane;//玩家战机
extern Aircraft Enemys[ENEMYNUMAX];//敌机
extern Ammo Supplys[SUPPLYNUM];//空投补给
extern Ammo Bullets[BULLETNUM];//子弹


struct game

//创建游戏结构体管理控制游戏的难度
typedef struct game{
int gamelevel;
int score;
int enemynum;//敌机数量
int enemyPCT1;
int enemyPCT2;//控制大中小敌机的比例
int speedPCT;//控制高速敌机的占比
int supplyPCT1;
int supplyPCT2;//控制空投出现的比例
int FPS;//储存游戏当前的FPS
}Game;

二、函数功能介绍

Game.cpp

//游戏画面的绘制,难度控制,音乐播放,游戏的开始暂停结束
void LoadImages(); //加载图片到内存
void InitGame(); //初始化游戏数据
void DrawINTF(); //绘制按钮,血条,炸弹等
void DrawPlane(); //绘制战机和敌机
void DrawAmmo(); //绘制子弹和空投补给
void LevelChack(); //根据得分情况控制游戏的难度
void Textout(); //一些文本内容的输出
void GameStart(); //绘制游戏开始界面
int GameOver(); //游戏结束等待用户选择
void Pause(); //游戏暂停
void PlayMusic(int type); //播放音乐


DealObject.cpp

//处理游戏角色的创建,移动,碰撞和摧毁
void CreatBullet(); //创建子弹
void CreatSupply(); //创建空投补给
void CreatEnemy(); //创建敌机
void PlayerMove(); //玩家移动
void BulletMove(); //子弹移动
void SupplyMove(); //空投移动
void EnemyMove(); //敌机移动
void PlaneFight(); //打飞机
void CrashPlane(); //撞飞机
void Destroy(); //摧毁飞机
void GetSupply(); //获得空投
void Bombing(); //炸弹轰炸


Tools.cpp

//一些游戏相关的工具
void CtrlFPS(clock_t start_time);
bool Timer(clock_t *ts, clock_t td);
bool Framer(int *cnt, int fd);
bool IsCollision(int x, int y, int type);
bool IsinTriangle(int x, int y);
void drawAlpha(IMAGE *dstimg, int x, int y, IMAGE *srcimg);


三、函数源码解析

1. 主函数

int main(){

	clock_t start_time = 0;
	
	srand((unsigned int)time(NULL));
	InitGame();
	LoadImages();
		
	initgraph(WIDTH, HEIGHT);
	GameStart();
	BeginBatchDraw();
	
	while (1)
	{
		start_time = clock();
			
		//绘制游戏图形
		DrawPlane();
		DrawAmmo();
		DrawINTF();

		//创建游戏角色
		CreatEnemy();
		CreatSupply();
		CreatBullet();
		
		//游戏角色移动
		PlayerMove();
		EnemyMove();
		SupplyMove();
		BulletMove();

		//游戏角色互动
		PlaneFight();
		CrashPlane();
		GetSupply();

		Pause();
		LevelChack();
		Destroy();

		switch (GameOver())
		{
		case RESUME:
			break;
		case AGAIN:
			InitGame();
			break;
		case GAMEOVER:
			goto END;
			break;
		default:
			break;
		}

		Textout();//必须放在绘图函数之后防止被图层覆盖
		CtrlFPS(start_time);
		FlushBatchDraw();
	}
	END:
	EndBatchDraw();
	closegraph();
	return 0;
}

2. 展示代码

void LoadImages();

void LoadImages(){

	//加载背景图片
	loadimage(&Img_bk, TEXT("res/background.png"), WIDTH, HEIGHT);
	loadimage(&Img_temp, TEXT("res/background.png"), WIDTH, HEIGHT);

	//加载按钮图片
	loadimage(&Img_buttons_nor[0], TEXT("res/pause_nor.png"), BUTTONSW, BUTTONSH);
	loadimage(&Img_buttons_nor[1], TEXT("res/resume_nor.png"), BUTTONSW, BUTTONSH);
	loadimage(&Img_buttons_nor[2], TEXT("res/again.png"), AGAINW, AGAINH);
	loadimage(&Img_buttons_nor[3], TEXT("res/gameover.png"), AGAINW, AGAINH);
	loadimage(&Img_buttons_pressed[0], TEXT("res/pause_pressed.png"), BUTTONSW, BUTTONSH);
	loadimage(&Img_buttons_pressed[1], TEXT("res/resume_pressed.png"), BUTTONSW, BUTTONSH);

	//加载一些杂七杂八的图片
	loadimage(&Img_states[0], TEXT("res/life.png"), LIFEW, LIFEH);
	loadimage(&Img_states[1], TEXT("res/bomb.png"), BOMBW, BOMBH);
	loadimage(&Img_bullets[0], TEXT("res/bullet1.png"), BULLETW, BULLETH);
	loadimage(&Img_bullets[1], TEXT("res/bullet2.png"), BULLETW, BULLETH);
	loadimage(&Img_supplys[0], TEXT("res/bullet_supply.png"), BULLET_SUPPLYW, BULLET_SUPPLYH);
	loadimage(&Img_supplys[1], TEXT("res/bomb_supply.png"), BULLET_SUPPLYW, BULLET_SUPPLYH);

	//加载战机
	loadimage(&Img_plane[0], TEXT("res/me1.png"), PLANEW, PLANEH);
	loadimage(&Img_plane[1], TEXT("res/me2.png"), PLANEW, PLANEH);
	loadimage(&Img_plane_destroy[0], TEXT("res/me_destroy_1.png"), PLANEW, PLANEH);
	loadimage(&Img_plane_destroy[1], TEXT("res/me_destroy_2.png"), PLANEW, PLANEH);
	loadimage(&Img_plane_destroy[2], TEXT("res/me_destroy_3.png"), PLANEW, PLANEH);
	loadimage(&Img_plane_destroy[3], TEXT("res/me_destroy_4.png"), PLANEW, PLANEH);

	//ENEMYA
	loadimage(&Img_enemya, TEXT("res/enemy1.png"), ENEMYAW, ENEMYAH);
	loadimage(&Img_enemya_destroy[0], TEXT("res/enemy1_down1.png"), ENEMYA_DESTROYW, ENEMYA_DESTROYH);
	loadimage(&Img_enemya_destroy[1], TEXT("res/enemy1_down2.png"), ENEMYA_DESTROYW, ENEMYA_DESTROYH);
	loadimage(&Img_enemya_destroy[2], TEXT("res/enemy1_down3.png"), ENEMYA_DESTROYW, ENEMYA_DESTROYH);
	loadimage(&Img_enemya_destroy[3], TEXT("res/enemy1_down4.png"), ENEMYA_DESTROYW, ENEMYA_DESTROYH);

	//ENEMYB
	loadimage(&Img_enemyb[0], TEXT("res/enemy2.png"), ENEMYBW, ENEMYBH);
	loadimage(&Img_enemyb[1], TEXT("res/enemy2_hit.png"), ENEMYBW, ENEMYBH);
	loadimage(&Img_enemyb_destroy[0], TEXT("res/enemy2_down1.png"), ENEMYB_DESTROYW, ENEMYB_DESTROYH);
	loadimage(&Img_enemyb_destroy[1], TEXT("res/enemy2_down2.png"), ENEMYB_DESTROYW, ENEMYB_DESTROYH);
	loadimage(&Img_enemyb_destroy[2], TEXT("res/enemy2_down3.png"), ENEMYB_DESTROYW, ENEMYB_DESTROYH);
	loadimage(&Img_enemyb_destroy[3], TEXT("res/enemy2_down4.png"), ENEMYB_DESTROYW, ENEMYB_DESTROYH);

	//ENEMYC
	loadimage(&Img_enemyc[0], TEXT("res/enemy3_n1.png"), ENEMYCW, ENEMYCH);
	loadimage(&Img_enemyc[1], TEXT("res/enemy3_n2.png"), ENEMYCW, ENEMYCH);
	loadimage(&Img_enemyc[2], TEXT("res/enemy3_hit.png"), ENEMYCW, ENEMYCH);
	loadimage(&Img_enemyc_destroy[0], TEXT("res/enemy3_down1.png"), ENEMYC_DESTROYW, ENEMYC_DESTROYH);
	loadimage(&Img_enemyc_destroy[1], TEXT("res/enemy3_down2.png"), ENEMYC_DESTROYW, ENEMYC_DESTROYH);
	loadimage(&Img_enemyc_destroy[2], TEXT("res/enemy3_down3.png"), ENEMYC_DESTROYW, ENEMYC_DESTROYH);
	loadimage(&Img_enemyc_destroy[3], TEXT("res/enemy3_down4.png"), ENEMYC_DESTROYW, ENEMYC_DESTROYH);
	loadimage(&Img_enemyc_destroy[4], TEXT("res/enemy3_down5.png"), ENEMYC_DESTROYW, ENEMYC_DESTROYH);
	loadimage(&Img_enemyc_destroy[5], TEXT("res/enemy3_down6.png"), ENEMYC_DESTROYW, ENEMYC_DESTROYH);

}

void InitGame();

void InitGame(){
	//初始化游戏结构体
	Mygame.gamelevel = 1;
	Mygame.score = 0;
	Mygame.enemynum = 10;
	Mygame.enemyPCT1 = 80;
	Mygame.enemyPCT2 = 100;
	Mygame.speedPCT = 30;
	Mygame.supplyPCT1 = 10;
	Mygame.supplyPCT2 = 20;
	Mygame.FPS = 0;

	//初始化战机结构体
	Plane.HP = 5;
	Plane.type = PLANE;
	Plane.speed = PLANESPEED;
	Plane.bomb_supply = 0;
	Plane.bullet_supply = 0;
	Plane.width = PLANEW;
	Plane.height = PLANEH;
	Plane.x = (WIDTH - Plane.width) / 2;
	Plane.y = HEIGHT / 3 * 2;
	Plane.state = 0;
	Plane.statemax = 6;
	memset(Plane.cnt, 0, sizeof(Plane.cnt));

	int i = 0;
	//初始化敌机结构体数组
	for (i = 0; i < ENEMYNUMAX; i++)
	{
		//将敌机初始化为“完全摧毁”状态
		Enemys[i].statemax = DESTROY;
		Enemys[i].state = Enemys[i].statemax + 1;
		memset(Enemys[i].cnt, 0, sizeof(Enemys[i].cnt));
	}

	//初始化补给、子弹的结构体数组
	for (i = 0; i < SUPPLYNUM; i++)
	{
		Supplys[i].isExist = false;
		Supplys[i].speed = SUPPLYSPEED;
	}
	for (i = 0; i < BULLETNUM; i++)
	{
		Bullets[i].isExist = false;
		Bullets[i].speed = BULLETSPEED;
		Bullets[i].width = BULLETW;
		Bullets[i].height = BULLETH;
	}
}

//将敌机初始化为“完全摧毁”状态
Enemys[i].statemax = DESTROY;//判断敌机已经被摧毁的标志
Enemys[i].state = Enemys[i].statemax + 1;//判断敌机已经完全消失的标志(播放完爆炸场景)


void DrawINTF();

void DrawINTF(){
	int i = 0;
	static int cnt = 0;

	//暂停按钮
	drawAlpha(&Img_temp, SPACING, SPACING, &Img_buttons_nor[0]);

	//继续按钮
	if (ResumeBottonDown)
	{
		if (!Framer(&cnt, 12))
		{
			//继续按钮被按下
			drawAlpha(&Img_temp, BUTTONSW + SPACING, SPACING, &Img_buttons_pressed[1]);
		}
		else
		{
			ResumeBottonDown = false;
		}
	}
	else
	{
		//继续按钮
		drawAlpha(&Img_temp, BUTTONSW + SPACING, SPACING, &Img_buttons_nor[1]);
	}


	//生命值
	for (i = 1; i <= Plane.HP; i++)
	{
		drawAlpha(&Img_temp, WIDTH - i * (LIFEW + SPACING), 10, &Img_states[0]);
	}
	//炸弹量
	for (i = 1; i <= Plane.bomb_supply; i++)
	{
		drawAlpha(&Img_temp, 10, HEIGHT - TEXT5 - i * (BOMBH + SPACING), &Img_states[1]);
	}

	putimage(0, 0, &Img_temp);
}

void DrawPlane();

void DrawPlane(){

	int i = 0;

	//绘制战机
	switch (Plane.state)
	{
	case NORMAL1://普通状态
		drawAlpha(&Img_temp, Plane.x, Plane.y, &Img_plane[0]);
		break;
	case NORMAL2://向前移动,加速状态
		drawAlpha(&Img_temp, Plane.x, Plane.y, &Img_plane[1]);
		//松开按键10帧后回到普通状态
		if (Framer(&Plane.cnt[NORMAL2], 10))
		{
			Plane.state = NORMAL1;
		}
		break;
	default:
		break;
	}

	//绘制敌机
	for (i = 0; i < Mygame.enemynum; i++)
	{
		//ENEMYA
		if (Enemys[i].type == ENEMYA && Enemys[i].state == NORMAL1)
		{
			drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemya);
		}

		//ENEMYB
		if (Enemys[i].type == ENEMYB)
		{
			switch (Enemys[i].state)
			{
			case NORMAL1:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyb[0]);
				break;
			case UNDERATTACK://受击状态
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyb[1]);
				if (Framer(&Enemys[i].cnt[UNDERATTACK], 10))
				{
					Enemys[i].state = NORMAL1;
				}
				break;
			default:
				break;
			}
		}

		//ENEMYC
		if (Enemys[i].type == ENEMYC)
		{
			switch (Enemys[i].state)
			{
			case NORMAL1:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyc[0]);
				if (Framer(&Enemys[i].cnt[NORMAL1], 10))
				{
					Enemys[i].state = NORMAL2;
				}
				break;
			case NORMAL2:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyc[1]);
				if (Framer(&Enemys[i].cnt[NORMAL2], 10))
				{
					Enemys[i].state = NORMAL1;
				}
				break;
			case UNDERATTACK:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyc[2]);
				if (Framer(&Enemys[i].cnt[UNDERATTACK], 10))
				{
					Enemys[i].state = NORMAL1;
				}
				break;
			default:
				break;
			}
		}
	}//end of for

	putimage(0, 0, &Img_temp);
}

void DrawAmmo();

void DrawAmmo(){

	int i = 0;

	//绘制子弹
	for (i = 0; i < BULLETNUM; i++)
	{
		if (Bullets[i].isExist)
		{
			if (Bullets[i].type == BULLET1)
			{
				drawAlpha(&Img_temp, Bullets[i].x, Bullets[i].y, &Img_bullets[0]);
			}
			else
			{
				drawAlpha(&Img_temp, Bullets[i].x, Bullets[i].y, &Img_bullets[1]);
			}

		}
	}//end of for

	//绘制空投补给
	for (i = 0; i < SUPPLYNUM; i++)
	{
		if (Supplys[i].isExist)
		{
			if (Supplys[i].type == BULLET_SUPPLY)
			{
				drawAlpha(&Img_temp, Supplys[i].x, Supplys[i].y, &Img_supplys[0]);
			}
			else
			{
				drawAlpha(&Img_temp, Supplys[i].x, Supplys[i].y, &Img_supplys[1]);
			}

		}
	}//end of for

	putimage(0, 0, &Img_temp);
}

void Textout();

void Textout(){
	wchar_t text[50] = { 0 };
	settextstyle(TEXT4, 0, TEXT("微软雅黑"));
	//得分
	swprintf(text, TEXT("SCORE : %-d"), Mygame.score);
	outtextxy(BUTTONSW * 2 + SPACING * 2, SPACING, text);
	//游戏等级
	swprintf(text, TEXT("LEVEL : %-d"), Mygame.gamelevel);
	outtextxy(WIDTH - textwidth(text) - SPACING, HEIGHT - textheight(text) - SPACING, text);
	//FPS
	settextstyle(TEXT5, 0, TEXT("黑体"));
	swprintf_s(text, TEXT("FPS:%d"), Mygame.FPS);
	outtextxy(SPACING, HEIGHT - textheight(text) - SPACING, text);
}

注意:文本输出函数必须在所有绘图函数后调用,防止被其他图层遮盖。


void GameStart();

void GameStart(){

	int textsize = TEXT3 / 6 * 5;
	int step = -1;
	wchar_t text[50];
	clock_t ts = 0;

	setbkmode(TRANSPARENT);
	BeginBatchDraw();
	
	do
	{
		if (Timer(&ts, 50))
		{
			//绘制背景和logo
			drawAlpha(&Img_temp, 0, 0, &Img_bk);
			drawAlpha(&Img_temp, (WIDTH - LOGOW) / 2, HEIGHT / 10, &Img_logo);
			putimage(0, 0, &Img_temp);

			//显示操作说明
			settextstyle(TEXT2, 0, TEXT("华文琥珀"));
			settextcolor(RGB(118, 123, 124));
			swprintf_s(text, TEXT("%s"), TEXT("W、S、A、D移动"));
			outtextxy((WIDTH - textwidth(text)) / 2, HEIGHT / 5 * 2, text);
			swprintf_s(text, TEXT("%s"), TEXT("空格键请求轰炸"));
			outtextxy((WIDTH - textwidth(text)) / 2, HEIGHT / 5 * 2 + textheight(text), text);

			//动态显示开始游戏的字样
			settextstyle(textsize, 0, TEXT("微软雅黑"));
			settextcolor(BLACK);
			swprintf_s(text, TEXT("%s"), TEXT("按空格键开始游戏!"));
			outtextxy((WIDTH - textwidth(text)) / 2, HEIGHT / 3 * 2 - textheight(text) / 2, text);

			if (textsize >= TEXT3 || textsize <= TEXT3 / 6 * 5)
			{
				step = -step;
			}
			textsize += step;

			FlushBatchDraw();
		}
		
	} while (!GetAsyncKeyState(VK_SPACE));

	EndBatchDraw();
	PlayMusic(GAMESTART);
	
}

void Pause();

void Pause(){

	clock_t ts = 0;
	
	//两个按钮的坐标
	int x1 = SPACING;
	int y1 = SPACING;
	int x2 = BUTTONSW + SPACING;
	int y2 = SPACING;

	//接受鼠标消息
	ExMessage msg;
	bool pause, resume;
	peekmessage(&msg, EX_MOUSE);
	//点击暂停按钮
	pause = msg.message == WM_LBUTTONDOWN && msg.x >= x1 && msg.y >= y1 && msg.x <= x1 + BUTTONSW && msg.y <= y1 + BUTTONSH;
	//点击继续按钮
	resume = msg.message == WM_LBUTTONDOWN && msg.x >= x2 && msg.y >= y2 && msg.x <= x2 + BUTTONSW && msg.y <= y2 + BUTTONSH;

	if (pause)
	{
		PlayMusic(BUTTON);
		PlayMusic(PAUSE);

		//暂停按钮被按下
		drawAlpha(&Img_temp, SPACING, SPACING, &Img_buttons_pressed[0]);
		putimage(0, 0, &Img_temp);
		ts = clock();//记录按钮按下的时刻,50ms后弹起
		FlushBatchDraw();

		wchar_t text[20] = TEXT("游戏暂停");
		settextstyle(TEXT3, 0, TEXT("微软雅黑"));
		do
		{
			if (Timer(&ts, 50))//每50ms刷新一帧
			{
				drawAlpha(&Img_temp, SPACING, SPACING, &Img_buttons_nor[0]);
				putimage(0, 0, &Img_temp);
				outtextxy((WIDTH - textwidth(text)) / 2, HEIGHT / 3 - textheight(text) / 2, text);
				FlushBatchDraw();
			}

			peekmessage(&msg, EX_MOUSE);
			resume = msg.message == WM_LBUTTONDOWN && msg.x >= x2 && msg.y >= y2 && msg.x <= x2 + BUTTONSW && msg.y <= y2 + BUTTONSH;
		} while (!resume);
	}//end of if

	if (resume)
	{
		PlayMusic(BUTTON);
		PlayMusic(RESUME);
		//继续按钮被按下
		ResumeBottonDown = true;
	}

}

void PlayMusic(int type);

void PlayMusic(int type){

	switch (type)
	{
	case GAMESTART:
		//重复播放BGM		
		mciSendString(TEXT("open res/game_music.mp3 alias bgm"), 0, 0, 0);
		mciSendString(TEXT("setaudio bgm volume to 500"), 0, 0, 0);//将BGM音量调小(阈值0-1000)
		mciSendString(TEXT("play bgm repeat"), 0, 0, 0);
		//重复播放子弹音效	
		mciSendString(TEXT("open res/bullet.mp3 alias bullet"), 0, 0, 0);
		mciSendString(TEXT("play bullet repeat"), 0, 0, 0);
		break;
	case BUTTON:
		mciSendString(TEXT("close button"), 0, 0, 0);
		mciSendString(TEXT("open res/button.mp3 alias button"), 0, 0, 0);
		mciSendString(TEXT("play button"), 0, 0, 0);
		break;
	case ENEMYA_DOWN:
		mciSendString(TEXT("close enemy1_down"), 0, 0, 0);
		mciSendString(TEXT("open res/enemy1_down.mp3 alias enemy1_down"), 0, 0, 0);
		mciSendString(TEXT("play enemy1_down"), 0, 0, 0);
		break;
	case ENEMYB_DOWN:
		mciSendString(TEXT("close enemy2_down"), 0, 0, 0);
		mciSendString(TEXT("open res/enemy2_down.mp3 alias enemy2_down"), 0, 0, 0);
		mciSendString(TEXT("play enemy2_down"), 0, 0, 0);
		break;
	case ENEMYC_DOWN:
		mciSendString(TEXT("close enemy3_down"), 0, 0, 0);
		mciSendString(TEXT("open res/enemy3_down.mp3 alias enemy3_down"), 0, 0, 0);
		mciSendString(TEXT("play enemy3_down"), 0, 0, 0);
		break;
	case ENEMYC_FLYING:
		mciSendString(TEXT("close enemy3_flying"), 0, 0, 0);
		mciSendString(TEXT("open res/enemy3_flying.mp3 alias enemy3_flying"), 0, 0, 0);
		mciSendString(TEXT("play enemy3_flying"), 0, 0, 0);
		break;
	case GET_BULLET:
		mciSendString(TEXT("close get_bullet"), 0, 0, 0);
		mciSendString(TEXT("open res/get_bullet.mp3 alias get_bullet"), 0, 0, 0);
		mciSendString(TEXT("play get_bullet"), 0, 0, 0);
		break;
	case GET_BOMB:
		mciSendString(TEXT("close get_bomb"), 0, 0, 0);
		mciSendString(TEXT("open res/get_bomb.mp3 alias get_bomb"), 0, 0, 0);
		mciSendString(TEXT("play get_bomb"), 0, 0, 0);
		break;
	case ME_DOWN:
		mciSendString(TEXT("close me_down"), 0, 0, 0);
		mciSendString(TEXT("open res/me_down.mp3 alias me_down"), 0, 0, 0);
		mciSendString(TEXT("play me_down"), 0, 0, 0);
		break;
	case SUPPLY:
		mciSendString(TEXT("close supply"), 0, 0, 0);
		mciSendString(TEXT("open res/supply.mp3 alias supply"), 0, 0, 0);
		mciSendString(TEXT("play supply"), 0, 0, 0);
		break;
	case UPGRADE:
		mciSendString(TEXT("close upgrade"), 0, 0, 0);
		mciSendString(TEXT("open res/upgrade.mp3 alias upgrade"), 0, 0, 0);
		mciSendString(TEXT("play upgrade"), 0, 0, 0);
		break;
	case USE_BOMB:
		mciSendString(TEXT("close use_bomb"), 0, 0, 0);
		mciSendString(TEXT("open res/use_bomb.mp3 alias use_bomb"), 0, 0, 0);
		mciSendString(TEXT("play use_bomb"), 0, 0, 0);
		break;
	case PAUSE:
		mciSendString(TEXT("pause bgm"), 0, 0, 0);
		mciSendString(TEXT("pause bullet"), 0, 0, 0);
		break;
	case RESUME:
		mciSendString(TEXT("resume bgm"), 0, 0, 0);
		mciSendString(TEXT("resume bullet"), 0, 0, 0);
		break;
	case GAMEOVER:
		mciSendString(TEXT("close bgm"), 0, 0, 0);
		mciSendString(TEXT("close bullet"), 0, 0, 0);
		mciSendString(TEXT("close button"), 0, 0, 0);
		mciSendString(TEXT("close enemy1_down"), 0, 0, 0);
		mciSendString(TEXT("close enemy2_down"), 0, 0, 0);
		mciSendString(TEXT("close enemy3_down"), 0, 0, 0);
		mciSendString(TEXT("close enemy3_flying"), 0, 0, 0);
		mciSendString(TEXT("close get_bullet"), 0, 0, 0);
		mciSendString(TEXT("close get_bomb"), 0, 0, 0);
		mciSendString(TEXT("close me_down"), 0, 0, 0);
		mciSendString(TEXT("close supply"), 0, 0, 0);
		mciSendString(TEXT("close upgrade"), 0, 0, 0);
		mciSendString(TEXT("close use_bomb"), 0, 0, 0);
		break;
	default:
		break;
	}

}

void PlayMover();

void PlayerMove(){

	if (Plane.state >= DESTROY)
	{
		return;
	}

	if (GetAsyncKeyState('W') || GetAsyncKeyState(VK_UP))
	{
		if (Plane.y > 0)
		{
			Plane.y -= Plane.speed;
			Plane.state = NORMAL2;
		}
	}
	if (GetAsyncKeyState('S') || GetAsyncKeyState(VK_DOWN))
	{
		if (Plane.y < HEIGHT - Plane.height)
		{
			Plane.y += Plane.speed;
		}
	}
	if (GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT))
	{
		if (Plane.x > -(Plane.width / 2 - 5))//此处-5是微调飞机的位置
		{
			Plane.x -= Plane.speed;
		}
	}
	if (GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT))
	{
		if (Plane.x < WIDTH - Plane.width / 2 - 5)
		{
			Plane.x += Plane.speed;
		}
	}
	if (GetAsyncKeyState(VK_SPACE) && Plane.bomb_supply > 0)
	{
		Bombing();
	}

}

void Bombing();

void Bombing(){

	int i = 0;
	static clock_t ts = 0;
	//2秒钟的延时避免了按一次空格连续使用轰炸
	if (!Timer(&ts, 2000))
	{
		return;
	}

	PlayMusic(USE_BOMB);
	//摧毁存活的所有敌机
	for (i = 0; i < Mygame.enemynum; i++)
	{
		if (Enemys[i].state < DESTROY)
		{
			Enemys[i].state = DESTROY;
		}
	}
	Plane.bomb_supply--;
}

void CreatBullet();

void CreatBullet(){

	int i = 0;
	int offset = 25;//两排子弹的偏移量
	static clock_t ts = 0;

	//控制先后发射的子弹的间距
	if (!Timer(&ts, 250))
	{
		return;
	}

	if (Plane.bullet_supply != 0)
	{
		//弹药补给持续20秒
		if (clock() - Plane.bullet_supply >= 20000)
		{
			Plane.bullet_supply = 0;
		}

		//获得弹药补给发射两排子弹
		for (size_t k = 0; k < 2; k++)
		{
			for (i = 0; i < BULLETNUM; i++)
			{
				if (!Bullets[i].isExist)
				{
					Bullets[i].isExist = true;
					Bullets[i].type = BULLET2;
					Bullets[i].x = Plane.x + Plane.width / 2 + offset;
					Bullets[i].y = Plane.y + 5;
					offset = -offset;
					break;
				}
			}
		}//end of for	
	}//end of if
	else
	{
		for (i = 0; i < BULLETNUM; i++)
		{
			if (!Bullets[i].isExist)
			{
				Bullets[i].isExist = true;
				Bullets[i].type = BULLET1;
				Bullets[i].x = Plane.x + Plane.width / 2;
				Bullets[i].y = Plane.y + 5;
				return;
			}
		}
	}//end of else
}

此处Plane.bullet_supply的使用有些混乱,它即用于判断是否获得弹药补给,又用来记录获得弹药补给的时刻控制持续时间。


void CreatSupply();

void CreatSupply(){

	int i = 0;
	int randnum = rand() % 100;
	static clock_t ts = 0;

	//每过3秒有几率产生一次空投
	if (!Timer(&ts, 3000) || randnum >= Mygame.supplyPCT2)
	{
		return;
	}
	PlayMusic(SUPPLY);

	for (i = 0; i < SUPPLYNUM; i++)
	{
		if (!Supplys[i].isExist)
		{
			Supplys[i].isExist = true;
			//空投补给类型随机
			if (randnum >= 0 && randnum < Mygame.supplyPCT1)
			{
				Supplys[i].type = BULLET_SUPPLY;
				Supplys[i].width = BULLET_SUPPLYW;
				Supplys[i].height = BULLET_SUPPLYH;
				Supplys[i].x = rand() % (WIDTH - Supplys[i].width);
				Supplys[i].y = -Supplys[i].height;
			}
			else if (randnum >= Mygame.supplyPCT1 && randnum < Mygame.supplyPCT2)
			{
				Supplys[i].type = BOMB_SUPPLY;
				Supplys[i].width = BOMB_SUPPLYW;
				Supplys[i].height = BOMB_SUPPLYH;
				Supplys[i].x = rand() % (WIDTH - Supplys[i].width);
				Supplys[i].y = -Supplys[i].height;
			}
			return;
		}//end of if
	}//end of for
}

void EnemyMove();

void EnemyMove(){

	int i = 0;

	for (i = 0; i < Mygame.enemynum; i++)
	{
		if (Enemys[i].state < DESTROY)
		{
			Enemys[i].y += Enemys[i].speed;
			if (Enemys[i].y > HEIGHT)
			{
				Enemys[i].state = Enemys[i].statemax + 1;
			}
		}
	}
}

void BulletMove();

void BulletMove(){

	int i = 0;

	for (i = 0; i < BULLETNUM; i++)
	{
		if (Bullets[i].isExist)
		{
			Bullets[i].y -= Bullets[i].speed;
			if (Bullets[i].y <= -Bullets[i].height)
			{
				Bullets[i].isExist = false;
			}
		}
	}
}

void SupplyMove();

void SupplyMove(){

	int i = 0;

	for (i = 0; i < SUPPLYNUM; i++)
	{
		if (Supplys[i].isExist)
		{
			Supplys[i].y += Supplys[i].speed;
			if (Supplys[i].y >= HEIGHT)
			{
				Supplys[i].isExist = false;
			}
		}
	}
}

void PlaneFight();

void PlaneFight(){

	int i = 0;
	int j = 0;

	for (i = 0; i < Mygame.enemynum; i++)
	{
		if (Enemys[i].state >= DESTROY)
		{
			continue;
		}
		
		for (j = 0; j < BULLETNUM; j++)
		{
			if (Bullets[j].isExist)
			{
				//判断子弹是否与敌机碰撞
				if (Bullets[j].x >= Enemys[i].x && Bullets[j].y >= Enemys[i].y
					&&Bullets[j].x <= Enemys[i].x + Enemys[i].width
					&&Bullets[j].y <= Enemys[i].y + Enemys[i].height)
				{
					Bullets[j].isExist = false;
					Enemys[i].HP--;
					
					if (Enemys[i].HP <= 0)
					{
						Enemys[i].state = DESTROY;
						//摧毁敌机加分并播放音效
						switch (Enemys[i].type)
						{
						case ENEMYA:
							PlayMusic(ENEMYA_DOWN);
							Mygame.score += 100;
							break;
						case ENEMYB:
							PlayMusic(ENEMYB_DOWN);
							Mygame.score += 300;
							break;
						case ENEMYC:
							PlayMusic(ENEMYC_DOWN);
							Mygame.score += 1000;
							break;
						default:
							break;
						}
					}
					else
					{
						Enemys[i].state = UNDERATTACK;
					}
				}
			}
		}//end of for
	}//end of for
}

void CrashPlane();

void CrashPlane(){

	int i = 0;

	if (Plane.state >= DESTROY)
	{
		return;
	}
	
	for (i = 0; i < Mygame.enemynum; i++)
	{
		if (Enemys[i].state >= DESTROY)
		{
			continue;
		}
		if (IsCollision(Enemys[i].x, Enemys[i].y, Enemys[i].type))
		{
			Enemys[i].state = DESTROY;
			Plane.HP--;
			if (Plane.HP <= 0)
			{
				Plane.state = DESTROY;
				PlayMusic(ME_DOWN);
			}
		}
	}//end of for
}

void GetSupply();

void GetSupply(){

	int i = 0;

	for (i = 0; i < SUPPLYNUM; i++)
	{
		if (!Supplys[i].isExist)
		{
			continue;
		}
		if (IsCollision(Supplys[i].x, Supplys[i].y, Supplys[i].type))
		{
			Supplys[i].isExist = false;
			if (Supplys[i].type == BULLET_SUPPLY)
			{
				Plane.bullet_supply = clock();
				PlayMusic(GET_BULLET);
			}
			else
			{
				PlayMusic(GET_BOMB);
				if (Plane.bomb_supply < 5)
				{
					Plane.bomb_supply++;
				}
			}
		}
	}//end of for
}

bool IsCollision(int x, int y, int type);

bool IsCollision(int x, int y, int type){

	//根据类型计算出对象的所有碰撞点,再一个一个进行判断
	switch (type)
	{
	case ENEMYA:
		if (IsinTriangle(x + ENEMYAW / 2, y + ENEMYAH)
			|| IsinTriangle(x, y + ENEMYAH / 3)
			|| IsinTriangle(x + ENEMYAW, y + ENEMYAH / 3)
			|| IsinTriangle(x + ENEMYAW / 2, y))
		{
			return true;
		}
		break;
	case ENEMYB:
		if (IsinTriangle(x + ENEMYBW / 3, y + ENEMYBH)
			|| IsinTriangle(x + ENEMYBW / 3 * 2, y + ENEMYBH)
			|| IsinTriangle(x, y + ENEMYBH / 3)
			|| IsinTriangle(x, y + ENEMYBH / 3 * 2)
			|| IsinTriangle(x + ENEMYBW, y + ENEMYBH / 3)
			|| IsinTriangle(x + ENEMYBW, y + ENEMYBH / 3 * 2))
		{
			return true;
		}
		break;
	case ENEMYC:
		if (IsinTriangle(x + ENEMYCW / 3, y + ENEMYCH)
			|| IsinTriangle(x + ENEMYCW / 3 * 2, y + ENEMYCH)
			|| IsinTriangle(x, y)
			|| IsinTriangle(x + ENEMYCW, y)
			|| IsinTriangle(x, y + ENEMYCH / 3)
			|| IsinTriangle(x, y + ENEMYCH / 3 * 2)
			|| IsinTriangle(x + ENEMYCW, y + ENEMYCH / 3)
			|| IsinTriangle(x + ENEMYCW, y + ENEMYCH / 3 * 2))
		{
			return true;
		}
		break;
	case BULLET_SUPPLY:

		if (IsinTriangle(x, y)
			|| IsinTriangle(x + BULLET_SUPPLYW, y)
			|| IsinTriangle(x + BULLET_SUPPLYW, y + BULLET_SUPPLYH)
			|| IsinTriangle(x, y + BULLET_SUPPLYH)
			|| IsinTriangle(x, y + BULLET_SUPPLYH / 2)
			|| IsinTriangle(x + BULLET_SUPPLYW, y + BULLET_SUPPLYH / 2))
		{
			return true;
		}
		break;
	case BOMB_SUPPLY:
		if (IsinTriangle(x, y)
			|| IsinTriangle(x + BOMB_SUPPLYW, y)
			|| IsinTriangle(x + BOMB_SUPPLYW, y + BOMB_SUPPLYH)
			|| IsinTriangle(x, y + BOMB_SUPPLYH)
			|| IsinTriangle(x, y + BOMB_SUPPLYH / 2)
			|| IsinTriangle(x + BOMB_SUPPLYW, y + BOMB_SUPPLYH / 2))
		{
			return true;
		}
		break;
	default:
		break;
	}
	return false;
}

bool IsinTriangle(int x, int y);

bool IsinTriangle(int x, int y){

	//定义6个变量记录三角形战机3个点的坐标
	int x1 = Plane.x + Plane.width / 2;
	int y1 = Plane.y;
	int x2 = Plane.x;
	int y2 = Plane.y + Plane.height;
	int x3 = Plane.x + Plane.width;
	int y3 = Plane.y + Plane.height;
	//计算这3个点构成的三角形的面积
	double s1 = 0.5 * fabs((double)((x1*y2 - x2*y1) + (x2*y3 - x3*y2) + (x3*y1 - x1*y3)));
	//计算待判断点和其中2个点构成的三角形的面积
	double s2 = 0.5 * fabs((double)((x*y2 - x2*y) + (x2*y3 - x3*y2) + (x3*y - x*y3)));
	double s3 = 0.5 * fabs((double)((x1*y - x*y1) + (x*y3 - x3*y) + (x3*y1 - x1*y3)));
	double s4 = 0.5 * fabs((double)((x1*y2 - x2*y1) + (x2*y - x*y2) + (x*y1 - x1*y)));

	if (fabs(s1 - s2 - s3 - s4) < DBL_EPSILON)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void CtrlFPS(clock_t start_time);

void CtrlFPS(clock_t start_time){

	clock_t running_time = clock() - start_time;

	int sleep_time = 13 - running_time;
	if (sleep_time > 0)
	{
		Sleep(sleep_time);
	}

	//记录经过动态休眠调整后的FPS	
	Mygame.FPS = 1000 / (clock() - start_time);
}

3. 详解代码

3.1 游戏难度自动调整

void CreatEnemy();
void CreatEnemy(){

	int i = 0;
	int randnum = 0;
	static clock_t ts = 0;

	//每半秒钟产生一架敌机
	if (!Timer(&ts, 500))
	{
		return;
	}

	for (i = 0; i < Mygame.enemynum; i++)
	{
		//必须是完全消失的敌机(爆炸动画播放完毕)
		if (Enemys[i].state == Enemys[i].statemax + 1)
		{
			Enemys[i].state = NORMAL1;
			//设置敌机类型,并根据类型设置其他属性
			randnum = rand() % 100;
			if (randnum >= 0 && randnum < Mygame.enemyPCT1)
			{
				Enemys[i].type = ENEMYA;
				Enemys[i].HP = 1;
				Enemys[i].width = ENEMYAW;
				Enemys[i].height = ENEMYAH;
				Enemys[i].statemax = 6;
				Enemys[i].x = rand() % (WIDTH - Enemys[i].width);
				Enemys[i].y = -Enemys[i].height;
			}
			else if (randnum >= Mygame.enemyPCT1 && randnum < Mygame.enemyPCT2)
			{
				Enemys[i].type = ENEMYB;
				Enemys[i].HP = 3;
				Enemys[i].width = ENEMYBW;
				Enemys[i].height = ENEMYBH;
				Enemys[i].statemax = 6;
				Enemys[i].x = rand() % (WIDTH - Enemys[i].width);
				Enemys[i].y = -Enemys[i].height;
			}
			else
			{
				Enemys[i].type = ENEMYC;
				Enemys[i].HP = 8;
				Enemys[i].width = ENEMYCW;
				Enemys[i].height = ENEMYCH;
				Enemys[i].statemax = 8;
				Enemys[i].x = rand() % (WIDTH - Enemys[i].width);
				Enemys[i].y = -Enemys[i].height;
				PlayMusic(ENEMYC_FLYING);
			}

			//设置敌机速度
			randnum = rand() % 100;
			switch (Enemys[i].type)
			{
			case ENEMYA:
				if (randnum >= 0 && randnum < Mygame.speedPCT)
				{
					Enemys[i].speed = ENEMYASPEED2;
				}
				else
				{
					Enemys[i].speed = ENEMYASPEED1;
				}
				break;
			case ENEMYB:
				if (randnum >= 0 && randnum < Mygame.speedPCT)
				{
					Enemys[i].speed = ENEMYBSPEED2;
				}
				else
				{
					Enemys[i].speed = ENEMYBSPEED1;
				}
				break;
			case ENEMYC:
				Enemys[i].speed = ENEMYCSPEED;
				break;
			default:
				break;
			}//end of switch

			return;
		}//end of if (Enemys[i].state == Enemys[i].statemax + 1)
	}//end of for

}

如何控制敌机各类型产生的比例?
在这里插入图片描述
速度和补给的比例原理也是如此


void LevelChack();
void LevelChack(){

	//查看是否需要升级,不需升级直接返回
	switch (Mygame.gamelevel)
	{
	case 1:
		if (Mygame.score >= 1000)
		{
			Mygame.gamelevel = 2;
			break;
		}
		return;
	case 2:
		if (Mygame.score >= 5000)
		{
			Mygame.gamelevel = 3;
			break;
		}
		return;
	case 3:
		if (Mygame.score >= 20000)
		{
			Mygame.gamelevel = 4;
			break;
		}
		return;
	case 4:
		if (Mygame.score >= 50000)
		{
			Mygame.gamelevel = 5;
			break;
		}
		return;
	default:
		return;
	}//end of switch

	PlayMusic(UPGRADE);

	//升级后改变游戏难度
	switch (Mygame.gamelevel)
	{
	case 2:
		Mygame.enemynum = 15;
		Mygame.enemyPCT1 = 60;
		Mygame.enemyPCT2 = 100;
		Mygame.speedPCT = 50;
		Mygame.supplyPCT1 = 10;
		Mygame.supplyPCT2 = 20;
		break;
	case 3:
		Mygame.enemynum = 20;
		Mygame.enemyPCT1 = 60;
		Mygame.enemyPCT2 = 98;
		Mygame.speedPCT = 50;
		Mygame.supplyPCT1 = 10;
		Mygame.supplyPCT2 = 20;
		break;
	case 4:
		Mygame.enemynum = 25;
		Mygame.enemyPCT1 = 50;
		Mygame.enemyPCT2 = 95;
		Mygame.speedPCT = 60;
		Mygame.supplyPCT1 = 15;
		Mygame.supplyPCT2 = 30;
		break;
	case 5:
		Mygame.enemynum = 30;
		Mygame.enemyPCT1 = 40;
		Mygame.enemyPCT2 = 90;
		Mygame.speedPCT = 60;
		Mygame.supplyPCT1 = 20;
		Mygame.supplyPCT2 = 40;
		break;
	default:
		break;
	}//end of switch

}

分数达到指定标准后,等级提升。同时,修改游戏结构体中难度相关的参数,实现难度自动调整。
若分数未达到指定标准,函数直接返回不进行任何操作。


3.2 接受鼠标消息

int GameOver();
int GameOver(){

	clock_t ts = 0;

	if (Plane.state <= Plane.statemax)
	{
		return RESUME;
	}

	mciSendString(TEXT("close bgm"), 0, 0, 0);
	mciSendString(TEXT("close bullet"), 0, 0, 0);

	//两个按钮的位置
	int x1 = (WIDTH - AGAINW) / 2;
	int y1 = HEIGHT / 4;
	int x2 = (WIDTH - AGAINW) / 2;
	int y2 = y1 + AGAINH + 20;

	//绘制两个按钮
	drawAlpha(&Img_temp, x1, y1, &Img_buttons_nor[2]);
	drawAlpha(&Img_temp, x2, y2, &Img_buttons_nor[3]);
	putimage(0, 0, &Img_temp);

	//接受鼠标消息,死循环等待用于点击
	ExMessage msg;
	bool again, gameover;
	do
	{
		if (Timer(&ts, 50))
		{
			//每50ms刷新一次画面,使窗口实现重绘
			FlushBatchDraw();
		}
		peekmessage(&msg, EX_MOUSE);
		again = (msg.message == WM_LBUTTONDOWN && msg.x >= x1 && msg.y >= y1 && msg.x <= x1 + AGAINW && msg.y <= y1 + AGAINH);
		gameover = (msg.message == WM_LBUTTONDOWN && msg.x >= x2 && msg.y >= y2 && msg.x <= x2 + AGAINW && msg.y <= y2 + AGAINH);
	} while (!again && !gameover);

	//结束???继续???
	if (again)
	{
		PlayMusic(BUTTON);
		PlayMusic(GAMESTART);
		return AGAIN;
	}
	else
	{
		PlayMusic(BUTTON);
		//关闭所有音乐
		Sleep(500);//休眠半秒,等待音乐播放完毕
		PlayMusic(GAMEOVER);
		return GAMEOVER;
	}
}

接受鼠标消息的一般步骤

  1. 定义 ExMessage类型的消息结构体msg.
  2. peekmesage();函数从消息队列中拉取一条消息
  3. switch语句选择指定的消息类型,如WM_LBUTTONDOWN(鼠标左击)
  4. 根据对应类型的消息做出相应的操作。

3.3 计时器和计帧器

void Destroy();
void Destroy(){

	int i = 0;

	//飞机已被摧毁,但未播放完爆炸动画
	if (Plane.state >= DESTROY && Plane.state <= Plane.statemax)
	{
		drawAlpha(&Img_temp, Plane.x, Plane.y, &Img_plane_destroy[Plane.state - DESTROY]);//数组下标从0开始

		//10帧后,绘制下一状态的贴图
		if (Framer(&Plane.cnt[DESTROY], 10))
		{
			Plane.state++;
		}
	}

	for (i = 0; i < Mygame.enemynum; i++)
	{
		if (Enemys[i].state >= DESTROY && Enemys[i].state <= Enemys[i].statemax)
		{
			switch (Enemys[i].type)
			{
			case ENEMYA:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemya_destroy[Enemys[i].state - DESTROY]);
				break;
			case ENEMYB:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyb_destroy[Enemys[i].state - DESTROY]);
				break;
			case ENEMYC:
				drawAlpha(&Img_temp, Enemys[i].x, Enemys[i].y, &Img_enemyc_destroy[Enemys[i].state - DESTROY]);
				break;
			default:
				break;
			}

			if (Framer(&Enemys[i].cnt[DESTROY], 10))
			{
				Enemys[i].state++;
			}
		}
	}//end of for

	putimage(0, 0, &Img_temp);
}

在绘制飞机摧毁爆炸的连续画面时,用到了计帧器。使每一个状态的画面完整连续的呈现出来。这里我尝试使用过计时器但丢失画面的问题很难解决(除非将所有的时间戳都设置为全局变量)。

bool Timer(clock_t *ts, clock_t td);
bool Timer(clock_t *ts, clock_t td){

	if (clock() - *ts >= td)
	{
		*ts = clock();
		return true;
	}
	else
	{
		return false;
	}
}

计时器的优点

  • 由于时间的长度是固定的,因此不受硬件环境和运行环境的干扰。
  • 时间给程序员的感觉更直观。

计时器的缺点

  • 在计时动作开始时需要调用clock();函数记录开始的时刻(时间戳)。
  • 但记录时刻的位置和调用计时器函数的位置往往不在同一个函数中。
  • 遇到这种情况解决方法只能是将时间戳定义为全局变量
  • 但如果代码中使用计时器的地方很多,就会使全局变量过多,从而变得混乱。

使用场景

  • 独立在帧数控制(CtrlFPS)之外的循环语句中,如Pause();、GameOver();等
  • 对连续和完整要求不高的动作,如CreatEnemy();,CreatBullet();

bool Framer(int *cnt, int fd);
bool Framer(int *cnt, int fd){

	if (*cnt == (fd-1))
	{
		*cnt = 0;
		return true;
	}
	else
	{
		(*cnt)++;//易错点
		return false;
	}
}

计帧器的优点

  • 使用简单方便,只需要在计帧器函数被调用的地方定义一个静态的帧数计数器即可。
  • 正确的使用会使画面完整连续。

计帧器的缺点

  • 游戏的帧数受不同硬件环境和运行环境的干扰。
  • 也就是说,同样绘制10帧画面不同的电脑和运行环境使用的时间不同。
  • 使得游戏画面的FPS不稳定,时而卡顿,时而丢失画面(FPS太高)
  • 因此,计帧器的使用必须在帧数控制(CtrlFPS)的范围内,将游戏的FPS稳定控制在75左右

使用场景

  • 在帧数控制(CtrlFPS)的范围内。例如:DrawINTF();
  • 需要绘制连续完整的动画。例如:Destroy();

3.4 drawAlpha绘制透明贴图

void drawAlpha(IMAGE *dstimg, int x, int y, IMAGE *srcimg);
void drawAlpha(IMAGE *dstimg, int x, int y, IMAGE *srcimg)
{
	// 变量初始化
	DWORD *dst = GetImageBuffer(dstimg);
	DWORD *src = GetImageBuffer(srcimg);
	int src_width = srcimg->getwidth();
	int src_height = srcimg->getheight();
	int dst_width = (dstimg == NULL ? getwidth() : dstimg->getwidth());
	int dst_height = (dstimg == NULL ? getheight() : dstimg->getheight());

	// 计算贴图的实际长宽
	int iwidth = (x + src_width > dst_width) ? dst_width - x : src_width;		// 处理超出右边界
	int iheight = (y + src_height > dst_height) ? dst_height - y : src_height;	// 处理超出下边界
	if (x < 0) { src += -x;				iwidth -= -x;	x = 0; }				// 处理超出左边界
	if (y < 0) { src += src_width * -y;	iheight -= -y;	y = 0; }				// 处理超出上边界

	// 修正贴图起始位置
	dst += dst_width * y + x;
	// 实现透明贴图
	for (int iy = 0; iy < iheight; ++iy)
	{
		for (int i = 0; i < iwidth; ++i)
		{
			int sa = ((src[i] & 0xff000000) >> 24);//获取阿尔法值
			if (sa != 0)//假如是完全透明就不处理
				if (sa == 255)//假如完全不透明则直接拷贝
					dst[i] = src[i];
				else//真正需要阿尔法混合计算的图像边界才进行混合
					dst[i] = ((((src[i] & 0xff0000) >> 16) + ((dst[i] & 0xff0000) >> 16) * (255 - sa) / 255) << 16) | ((((src[i] & 0xff00) >> 8) + ((dst[i] & 0xff00) >> 8) * (255 - sa) / 255) << 8) | ((src[i] & 0xff) + (dst[i] & 0xff) * (255 - sa) / 255);
		}
		dst += dst_width;
		src += src_width;
	}
}

一些常见错误:

  1. 图片的格式不是.png格式,或者图片没有透明背景。
  2. 没有为临时图片变量(Img_temp),加载背景图片。(背景为空)
  3. 在绘图循环中没有调用drawAlpha();函数,将背景图片加载在临时图片变量(Img_temp)中。(背景只绘制一次,对象移动会留下残影)
  4. 调用完drawAlpha();函数后没有将临时图片变量(Img_temp)绘制在绘图窗口上(利用putimage();函数)。
  5. 没有及时刷新批量绘图(FlushBatchDraw();函数)
  6. 没有控制好图层显示的先后顺序,造成图层的遮盖。
  7. 注意:文本输出函数必须在所有绘图函数后调用,防止被其他图层遮盖。

四、游戏效果展示

【C语言游戏】微信飞机大战 | PlaneFight

五、 源码及游戏素材分享

游戏源码点这里(素材在项目文件夹的res目录下)

游戏素材点这里(百度网盘 | 提取码:z20g)

  • 8
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芥末虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值