C++用EGE簡單實現別踩白塊游戲


本項目已開源:https://github.com/wmpscc/AvoidBlank

關於EGE

  • 介紹:EGE(Easy Graphics Engine),是windows下的簡易繪圖庫,是一個類似BGI(graphics.h)的面向C/C++語言新手的圖形庫,它的目標也是為了替代TC的BGI庫而存在。
    詳情見EGE官網
  • 下載:官網提供的下載地址
  • 安裝方法一
  • 安裝方法二

簡單示例

#include <graphics.h>

int main()
{
        //初始化為640*480大小
        initgraph(640, 480);

        //等待用戶按鍵
        getch();

        //關閉圖形界面
        closegraph();
        return 0;
}

別踩白塊 -准備篇

開始之前先介紹一下相關點。

本篇文章假定你已經准備好游戲每個按鍵對應的音頻文件和游戲關卡的歌詞按鍵譜。

繪圖函數

  • setinitmode(RENDER_MANUAL); //設置窗口模式
  • 功能:
    這個函數用於設置初始化圖形的選項和模式。

  • 聲明:
    void setinitmode(int mode, int x = CW_USEDEFAULT, int y = CW_USEDEFAULT);

  • 參數:
    mode
    初始化模式,是二進制組合的值。如果為INIT_DEFAULT 表示使用默認值。
    可以使用的值的組合:
    INIT_NOBORDER 為無邊框窗口
    INIT_CHILD 為子窗口(需要使用attachHWND指定要依附的父窗口,此函數不另說明)
    INIT_TOPMOST 使窗口總在最前
    INIT_RENDERMANUAL 手動更新標志,即調用delay_fps/delay_ms等會等待操作的函數時會更新窗口,否則保持窗口內容
    INIT_WITHLOGO 使initgraph的時候顯示開場動畫logo
    INIT_NOFORCEEXIT 使關閉窗口的時候不強制退出程序,但窗口會消失,需要配合is_run函數
    INIT_DEFAULT 默認參數,不調用本函數時即使用此參數
    INIT_ANIMATION 是INIT_DEFAULT, INIT_RENDERMANUAL, INIT_NOFORCEEXIT的組合,用於動畫編寫

  • x, y
    初始化時窗口左上角在屏幕的坐標,默認為系統分配。

  • 返回值:
    (無)

  • 說明:
    本函數只能在initgraph前調用。

  • initgraph(WINDOW_WIDTH, WINDOW_HIGHT, INIT_RENDERMANUAL); //初始化窗口
  • 功能:
    這個函數用於初始化繪圖環境。

  • 聲明:

 void initgraph(
     int Width,
     int Height,
     int Flag = INIT_DEFAULT
 );
 void initgraph(
     int* gdriver,
     int* gmode,
     char* path
 ); // 兼容 Borland C++ 3.1 的重載,不建議使用。
  • 參數:

    • Width
      繪圖環境的寬度。如果為-1,則使用屏幕的寬度

    • Height
      繪圖環境的高度。如果為-1,則使用屏幕的高度

    • Style
      請留空,為保留參數

  • 返回值:
    (無)

  • setbkcolor(EGERGB(0xff, 0xff, 0xff)); //背景色
  • 功能:
    這個函數用於設置當前背景色。並且會把當前圖片上是原背景色的像素,轉變為新的背景色。

  • 聲明:

void setbkcolor(
     color_t color,
     PIMAGE pimg = NULL
 );
  • 參數:
    color
    指定要設置的背景顏色。注意,該設置會同時影響文字背景色。

  • 返回值:
    (無)

  • setfillstyle(SOLID_FILL, Blank_COLOR); //設置方塊顏色
  • 功能:
    這個函數用於設置當前填充類型。該函數的自定義填充部分尚不支持。

  • 聲明:

void setfillstyle(
    int pattern,
    color_t color,
    PIMAGE pimg = NULL
);
  • 參數:
    • pattern
      填充類型,可以是以下宏或值:

NULL_FILL 不填充

SOLID_FILL 固實填充

pupattern
color
填充顏色。

  • 返回值:
    (無)
  • fillpoly(4, point, totalimg); //繪制方塊
  • 功能:
    這個函數用於畫填充的多邊形。邊線顏色由setcolor函數決定,填充顏色由setfillstyle函數決定

  • 聲明:

void fillpoly(
     int numpoints,
     const int *polypoints,
     PIMAGE pimg = NULL
 );
  • 參數:
    numpoints
    多邊形點的個數。

polypoints
每個點的坐標,數組元素個數為 numpoints * 2。
該函數會自動連接多邊形首尾。

  • 返回值:
    (無)
  • setfont(40, 0, "宋體"); //設定字體樣式
  • 功能:
    這個函數用於設置當前字體樣式。

  • 聲明:

 void setfont(
     int nHeight,
     int nWidth,
     LPCSTR lpszFace,
     PIMAGE pimg = NULL
 );
 void setfont(
     int nHeight,
     int nWidth,
     LPCSTR lpszFace,
     int nEscapement,
     int nOrientation,
     int nWeight,
     bool bItalic,
     bool bUnderline,
     bool bStrikeOut,
     PIMAGE pimg = NULL
 );
 void setfont(
     int nHeight,
     int nWidth,
     LPCSTR lpszFace,
     int nEscapement,
     int nOrientation,
     int nWeight,
     bool bItalic,
     bool bUnderline,
     bool bStrikeOut,
     BYTE fbCharSet,
     BYTE fbOutPrecision,
     BYTE fbClipPrecision,
     BYTE fbQuality,
     BYTE fbPitchAndFamily,
     PIMAGE pimg = NULL
 );

 void setfont(
     const LOGFONT *font,
     PIMAGE pimg = NULL
 );
  • sprintf(mCharsScore, "%d", mScore); //int轉char*
  • setfontbkcolor(EGERGB(0xff, 0x00, 0x00)); //設置字體背景色
  • outtextxy(WINDOW_WIDTH / 2, 0, mCharsScore); //在窗口輸出字體
  • 功能:
    這個函數用於在指定位置輸出字符串。

  • 聲明:

 void outtextxy(
     int x,
     int y,
     LPCSTR textstring,
     PIMAGE pimg = NULL
 );
 void outtextxy(
     int x,
     int y,
     CHAR c,
     PIMAGE pimg = NULL
 );
 void outtextxy(
     int x,
     int y,
     LPCWSTR textstring,
     PIMAGE pimg = NULL
 );
 void outtextxy(
     int x,
     int y,
     WCHAR c,
     PIMAGE pimg = NULL
 );
  • 參數:
    x
    字符串輸出時頭字母的 x 軸的坐標值
    y
    字符串輸出時頭字母的 y 軸的坐標值。
    textstring
    要輸出的字符串的指針。
    c
    要輸出的字符。

  • 返回值:
    (無)

  • cleardevice(); //清屏
  • setcaption("別踩白塊"); //設置窗口標題

時間和隨機數

  • api_sleep(500); //傳入參數為延時多少毫秒,注意只有該函數可在線程中使用
  • delay_fps(FPS) //根據FPS設置延遲
  • randomize(); //初始化隨機數
  • random(max); //產生[0,max)的隨機數,注意不包括最大值

mp3音頻播放

以下兩種方法具體介紹看這里

filename 為要播放的文件的路徑名

  • 方法一:mciSendString
mciSendString(filename, NULL, 0, NULL); //低效播放方式
  • 方法二:mciSendCommand
    //高效率無阻塞播放
	ThreadParameter *tp = (ThreadParameter *)pParam;
	MCI_OPEN_PARMS open;//定義打開結構體變量

	open.lpstrElementName = filename;//填充參數

	mciSendCommand(0, MCI_OPEN, MCI_OPEN_ELEMENT, DWORD_PTR(&open));//打開
	MCI_PLAY_PARMS play;//定義播放結構題變量
	mciSendCommand(open.wDeviceID, MCI_PLAY, 0, DWORD_PTR(&play));//非阻塞式播放

線程

舉個例子

typedef struct ThreadParameter {	//音頻播放線程參數
	char filename[200];
	char key[30];
};


ThreadParameter tpp;	//音頻播放線程傳參結構體<全局變量>
DWORD WINAPI music_play(LPVOID pParam)	//播放音樂
{
	//高效率無阻塞播放
	ThreadParameter *tp = (ThreadParameter *)pParam;
	MCI_OPEN_PARMS open;//定義打開結構體變量
	open.lpstrElementName = tp->filename;//填充參數
	mciSendCommand(0, MCI_OPEN, MCI_OPEN_ELEMENT, DWORD_PTR(&open));//打開
	MCI_PLAY_PARMS play;//定義播放結構題變量
	mciSendCommand(open.wDeviceID, MCI_PLAY, 0, DWORD_PTR(&play));//非阻塞式播放
	return 0;
}

啟動線程

CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tpp, 0, NULL);	//啟動音樂播放線程

解釋如下:
結構體是用來傳參數的,線程函數返回值類型必須為DWORD WINAPI,函數參數必須為LPVOID 類型。形式如下:

  • DWORD WINAPI functionName(LPVOID pParam){}

啟動函數第四個參數傳入傳參結構體指針,此處為&tpp。形式如下:

  • CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)functionName, &tpp, 0, NULL); //啟動音樂播放線程

文件讀入

    FILE *fp = NULL;
	fp = fopen(filename, "r");  //filename為要打開的文件路徑

無回顯讀取按鍵

  • getch(); //返回值為按下的鍵對應ASII碼

別踩白塊 -啟程

效果圖

選擇關卡
游戲效果
游戲結束

我已經准備好每個按鍵的音頻文件和每個關卡對應音樂的鍵譜了,相關定義如下

#define Path_Music_resPiano "..//resplus//piano"	//載入鍵音
#define Path_Music_Fail "..//resplus//fail.mp3"
#define Path_Music_Launch "..//resplus//chuanshao.mp3"
#define Path_pre "..//resplus//piano//"	//音頻路徑前綴
#define Path_end ".mp3"	//音頻路徑后綴

//每個關卡對應鍵譜
char Path_A_Breeze_From_Alabama[] = "..//resplus//A Breeze From Alabama.txt";
char Path_Happy_New_Year[] = "..//resplus//Happy New Year.txt";
char Path_Jasmine[] = "..//resplus//Jasmine.txt";
char Path_Little_Star[] = "..//resplus//Little Star.txt";
char Path_lyricwaltz[] = "..//resplus//lyricwaltz.txt";
char Path_Merry_Christmas[] = "..//resplus//Merry Christmas.txt";

整體思路(這可能是最簡便的方法了)

  • 啟動時讀入相關的音樂鍵譜,記錄總按鍵數
  • 別踩白塊可看成豎向的四條平行軌道
  • 根據總按鍵數創建一個尺寸為 窗口寬度(按鍵數黑塊高度)的PIMAGE對象(此處代稱總畫布)
  • 生成1到4的隨機數,對應的數為所在的軌道。並且記錄隨機數,為后面評判做准備
  • 按照所在軌道在總畫布上錯位繪制黑塊
  • 游戲啟動后,使總畫布左下角定點與窗口(0,0)頂點對齊
  • 使總畫布在可見區域由底向上繪制,並記錄下移坐標。
  • 根據下移的坐標除以黑塊的高度可確定是哪個方塊
  • 根據按鍵以及之前的隨機數數組即答案來計分

具體實現

  • 主函數
    下面代碼已有注解
int main()	//主函數
{
	//--------播放啟動音樂----------begin
	ThreadParameter tp;
	strcpy(tp.filename, Path_Music_Launch);
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
	//--------播放啟動音樂----------end

	//------------初始化------------begin
	setinitmode(RENDER_MANUAL);
	initgraph(WINDOW_WIDTH, WINDOW_HIGHT, INIT_RENDERMANUAL);	//初始化窗口
	strcpy(Path_Music, Path_Jasmine);	//默認關卡
	//------------初始化------------end

	//----------選擇關卡------------begin
	setfont(20, 0, "宋體");
	outtextxy(10, 10, "按下對應字母進入關卡");
	outtextxy(10, 35, "A:	A Breeze From Alabama");
	outtextxy(10, 55, "B:	茉莉花");
	outtextxy(10, 75, "C:	Happy New Year");
	outtextxy(10, 95, "D:	Little Star");
	outtextxy(10, 115, "E:	lyricwaltz");
	outtextxy(10, 135, "F:	Merry Christmas");
	switch (getch())
	{
	case 'A':
		strcpy(Path_Music, Path_A_Breeze_From_Alabama);
		break;
	case 'B':
		strcpy(Path_Music, Path_Jasmine);
		break;
	case 'C':
		strcpy(Path_Music, Path_Happy_New_Year);
		break;
	case 'D':
		strcpy(Path_Music, Path_Little_Star);
		break;
	case 'E':
		strcpy(Path_Music, Path_lyricwaltz);
		break;
	case 'F':
		strcpy(Path_Music, Path_Merry_Christmas);
		break;
	default:
		break;
	}
	//----------選擇關卡------------end
	initFile();	//讀入相關文件
	creatView();	//創建地圖
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GameControl, NULL, 0, NULL);	//啟動鍵盤控制線程

	GameView();	//繪圖
	getch();	//防止游戲結束后退出
	return 0;
}
  • 文件讀入

鍵譜文件中的內容(片段供參考)

d2 d2 #f2 e2 a1 #c2 e2 d2 d (#f.a) (#f.a) d (#f.a) (#f.a) d (#f.a) (#f.a) A-1 (e.g) (e.g)

對應的音頻文件名為黃色部分

d2 d (#f.a) A-1

char track[1024][12];	//樂譜
int mLength = 0;	//鍵總數

void initFile()	//初始化文件資源
{
	FILE *fp = NULL;
	fp = fopen(Path_Music, "r");
	if (fp != NULL)
	{
		while (fscanf(fp, "%s", track[mLength]) != EOF)
		{	//讀入所有鍵
			mLength++;
		}
	}
	fclose(fp);
}

從文件中讀入每個按鍵的標識碼存到數組track中,並記錄有多少個鍵存到變量mLength中。

  • 繪制總畫布

根據總按鍵數創建一個尺寸為窗口寬度(按鍵數黑塊高度)的PIMAGE對象。生成1到4的隨機數,對應的數為所在的軌道。按照所在軌道在總畫布上錯位繪制黑塊

PIMAGE totalimg;    //總畫布存儲對象 <全局變量>
int randtrack[3000] = { -1000 };	//存軌道的隨機數

void creatView()
{
	int point[8];	//四邊形點集
	int x = 0, y = 0;	//基定點坐標
	randomize();	//初始化隨機數
	totalimg = newimage(WINDOW_WIDTH, Blank_HIGHT*mLength);	//初始化總畫布
	setbkcolor(COLOR_WHITE, totalimg);	//設置總畫布背景
	cleardevice(totalimg);	//清空總畫布
	for (int i = 0; i < mLength; i++)
	{
		randtrack[i] = random(4) + 1;	//參數0-3的隨機數,加一后為軌道位置
		y = inter_y;
		x = (randtrack[i] - 1) * Blank_WIDTH;
		point[0] = x;
		point[1] = y;
		point[2] = x + Blank_WIDTH;
		point[3] = y;
		point[4] = x + Blank_WIDTH;
		point[5] = y + Blank_HIGHT;
		point[6] = x;
		point[7] = y + Blank_HIGHT;
		setfillstyle(SOLID_FILL, Blank_COLOR);	//設置方塊顏色
		fillpoly(4, point, totalimg);	//繪制方塊
		inter_y += Blank_HIGHT;	//設置滑塊公共高度
	}
}
  • 啟動監聽按鍵線程

根據按鍵以及之前的隨機數數組即答案來計分
由於監聽按鍵和繪制動畫是異步操作的,所有我們給按鍵控制單獨開一個線程來監聽。線程的使用在上面已經介紹過了,下面來看看代碼。


DWORD WINAPI GameControl()	//控制
{
	int curr = 0;
	int k = 0, index = 0;
	ThreadParameter tp;
	strcpy(tp.filename, Path_Music_Fail);	//給線程傳參結構體賦值失敗音樂路徑
	for (; k != key_esc;)
	{
		if (kbhit())
		{
			k = getch();
			if (k == 'S')
			{
				if (randtrack[mLength - curr - 1] == 1)
				{
					music_play_control(track[index++]);		//將鍵音傳入音樂處理函數
					mScore++;	//得分加一
					drawCountText();	//刷新計分標簽
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);	//播放失敗音樂線程
					failplay = true;	//設定失敗標志為真
				}
			}
			else if (k == 'D')
			{
				if (randtrack[mLength - curr - 1] == 2)
				{
					music_play_control(track[index++]);
					mScore++;
					drawCountText();
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
					failplay = true;
				}
			}
			else if (k == '4')
			{
				if (randtrack[mLength - curr - 1] == 3)
				{
					music_play_control(track[index++]);
					mScore++;
					drawCountText();
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
					failplay = true;
				}
			}
			else if (k == '5')
			{
				if (randtrack[mLength - curr - 1] == 4)
				{
					music_play_control(track[index++]);
					mScore++;
					drawCountText();
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
					failplay = true;
				}
			}
			curr++;
			accelerate = mScore / 25;	//設定滑塊每25分增加速度

		}
		if (failplay)
		{
			api_sleep(500);	//等待播放音樂線程啟動
			return 0;
		}
	}
	return 0;
}

代碼中用大寫字母S、D和數字鍵4、5分別控制四條軌道,可隨意修改。
failpaly為游戲失敗標志,若為真則播放失敗音樂,延遲500毫秒后結束線程。

  • 計數器
    游戲窗口中有顯示得分情況,以下是繪制得分函數,其中mScore為int型變量,程序中通過改變mScore變量的值來改變得分。
void drawCountText()	//繪制計數器
{
	sprintf(mCharsScore, "%d", mScore);		//int轉char*
	setfont(40, 0, "宋體");		//設定字體樣式
	setfontbkcolor(EGERGB(0xff, 0x00, 0x00));	//設置字體背景色
	outtextxy(WINDOW_WIDTH / 2, 0, mCharsScore);	//在窗口輸出字體
}
  • 音頻播放
    該部分分為兩個函數,一個是預處理一個是播放。

預處理(寫法比較暴力哈)

void music_play_control(char key[])	//控制音樂播放
{
	int flage = 0;
	char copy[30];
	char buff[10][30];	//同時按下的鍵值
	char temp[200];
	memset(temp, '\0', sizeof(temp));
	//按照特定格式讀入每個鍵符
	if (key[0] != '(')
	{
		strcat(temp, Path_pre);
		strcat(temp, key);
		strcat(temp, Path_end);
		strcpy(tpp.filename, temp);
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tpp, 0, NULL);
	}
	else
	{
		memset(buff, '\0', sizeof(buff));
		strcpy(copy, key);
		for (int i = 0; i < strlen(copy); i++)
		{
			if (copy[i] == '.')
			{
				flage++;
				copy[i] = ' ';
			}
			else if (copy[i] == '(' || copy[i] == ')')
			{
				copy[i] = ' ';
			}
		}
		if (flage == 0)
		{
			sscanf(copy, "%s", buff[0]);
		}
		else if (flage == 1)
		{
			sscanf(copy, "%s %s", buff[0], buff[1]);
		}
		else if (flage == 2)
		{
			sscanf(copy, "%s %s %s", buff[0], buff[1], buff[2]);
		}
		else if (flage == 3)
		{
			sscanf(copy, "%s %s %s %s", buff[0], buff[1], buff[2], buff[3]);
		}
		for (int i = 0; i < flage; i++)
		{
			strcat(temp, Path_pre);
			strcat(temp, buff[i]);
			strcat(temp, Path_end);
			strcpy(tpp.filename, temp);
			CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tpp, 0, NULL);	//啟動音樂播放線程
			api_sleep(200);
		}
	}
}

實際播放音樂的線程函數

DWORD WINAPI music_play(LPVOID pParam)	//播放音樂
{
	//mciSendString(tp->filename, NULL, 0, NULL); //低效播放方式

	//高效率無阻塞播放
	ThreadParameter *tp = (ThreadParameter *)pParam;
	MCI_OPEN_PARMS open;//定義打開結構體變量
	open.lpstrElementName = tp->filename;//填充參數
	mciSendCommand(0, MCI_OPEN, MCI_OPEN_ELEMENT, DWORD_PTR(&open));//打開
	MCI_PLAY_PARMS play;//定義播放結構題變量
	mciSendCommand(open.wDeviceID, MCI_PLAY, 0, DWORD_PTR(&play));//非阻塞式播放
	return 0;
}

  • 啟動時繪制窗口函數
void GameView()	//	繪圖
{
	randomize();	//初始化隨機數
	cleardevice();	//清屏
	setcaption("別踩白塊 - 1707004544");		//窗口標題
	setbkcolor(EGERGB(0xff, 0xff, 0xff));	//背景色

	ThreadDrawTrack();
}
  • 游戲開始后畫面繪制線程函數

游戲啟動后,使總畫布左下角定點與窗口(0,0)頂點對齊
使總畫布在可見區域由底向上繪制,並記錄下移坐標。
根據下移的坐標除以黑塊的高度可確定是哪個方塊

DWORD WINAPI ThreadDrawTrack()
{
	setrendermode(RENDER_MANUAL);
	int y = inter_y;
	int addh = 0;
	ThreadParameter tp;
	strcpy(tp.filename, Path_Music_Fail);
	for (; is_run(); delay_fps(FPS))
	{
		if (failplay)
		{
			cleardevice();
			sprintf(mCharsScore, "%d", mScore);
			setfont(100, 0, "宋體");
			setfontbkcolor(EGERGB(0xff, 0x00, 0x00));
			outtextxy(WINDOW_WIDTH / 2 + 20, WINDOW_HIGHT / 2, mCharsScore);
			outtextxy(0, WINDOW_HIGHT / 2, "得分:");
			break;
		}
		if (-1 * y >= 0)
		{
			break;
		}
		putimage(0, -1 * y, totalimg);
		y -= 2 + accelerate;
		below = addh - mScore*Blank_HIGHT - 160;
		if (below >= WINDOW_HIGHT)
		{
			CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
			failplay = true;
		}
		addh += 2;
		drawCountText();	//更新顯示得分
	}
	return 1;
}

總結

個人感覺EGE繪制效率確實有點低,這是我寫的五總方法中畫面最流暢,且最簡單的方法。缺點也很明顯,不能單獨控制每個按鍵。
歡迎關注我的github

我也是初學者,代碼上有很多不足歡迎提出指正,感謝閱覽。

完整實現代碼如下

#include <graphics.h>
#include <math.h>
#include<string.h>
#include<stdio.h>
#include <windows.h>  
#pragma comment(lib, "Winmm.lib")            //For MCI(Media Control Interface,媒體控制接口)  

#define Path_Music_resPiano "..//resplus//piano"	//載入鍵音
#define Path_Music_Fail "..//resplus//fail.mp3"
#define Path_Music_Launch "..//resplus//chuanshao.mp3"
#define Path_pre "..//resplus//piano//"	//音頻路徑前綴
#define Path_end ".mp3"	//音頻路徑后綴
#define WINDOW_HIGHT 800	//窗口高度
#define WINDOW_WIDTH 480	//窗口寬度
#define Blank_HIGHT 160		//方塊高度
#define Blank_WIDTH 120		//方塊寬度
#define Blank_COLOR EGERGB(00,00,00)	//方塊顏色
#define COLOR_WHITE EGERGB(0xFF, 0xFF, 0xFF)	//白色
#define FPS 100
#define SLEEP 10

//-------函數聲明--------
void drawCountText();	//繪制計數器
void initFile();	//初始化文件資源
DWORD WINAPI music_play(LPVOID pParam); 	//播放音樂
void music_play_control(char key[]);	//控制音樂播放
DWORD WINAPI GameControl();		//控制
void GameView();	//	繪圖
void creatView();	//創建地圖

												//-------全局變量--------
int mScore = 0;	//分數
char mCharsScore[50];	//分數字符串
char track[1024][12];	//樂譜
int mLength = 0;	//鍵總數
int below = 0;	//最下面一個方塊定點的y軸
PIMAGE totalimg;
int randtrack[3000] = { -1000 };	//存軌道的隨機數
int inter_y = 0;	//生成圖片時用到的y
bool failplay = false;
int accelerate = 0;

char Path_A_Breeze_From_Alabama[] = "..//resplus//A Breeze From Alabama.txt";
char Path_Happy_New_Year[] = "..//resplus//Happy New Year.txt";
char Path_Jasmine[] = "..//resplus//Jasmine.txt";
char Path_Little_Star[] = "..//resplus//Little Star.txt";
char Path_lyricwaltz[] = "..//resplus//lyricwaltz.txt";
char Path_Merry_Christmas[] = "..//resplus//Merry Christmas.txt";
char Path_Music[50];

typedef struct ThreadParameter {	//音頻播放線程參數
	char filename[200];
	char key[30];
};

ThreadParameter tpp;	//音頻播放線程傳參結構體
int main()	//主函數
{
	//--------播放啟動音樂----------begin
	ThreadParameter tp;
	strcpy(tp.filename, Path_Music_Launch);
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
	//--------播放啟動音樂----------end

	//------------初始化------------begin
	setinitmode(RENDER_MANUAL);
	initgraph(WINDOW_WIDTH, WINDOW_HIGHT, INIT_RENDERMANUAL);	//初始化窗口
	strcpy(Path_Music, Path_Jasmine);	//默認關卡
	//------------初始化------------end

	//----------選擇關卡------------begin
	setfont(20, 0, "宋體");
	outtextxy(10, 10, "按下對應字母進入關卡");
	outtextxy(10, 35, "A:	A Breeze From Alabama");
	outtextxy(10, 55, "B:	茉莉花");
	outtextxy(10, 75, "C:	Happy New Year");
	outtextxy(10, 95, "D:	Little Star");
	outtextxy(10, 115, "E:	lyricwaltz");
	outtextxy(10, 135, "F:	Merry Christmas");
	switch (getch())
	{
	case 'A':
		strcpy(Path_Music, Path_A_Breeze_From_Alabama);
		break;
	case 'B':
		strcpy(Path_Music, Path_Jasmine);
		break;
	case 'C':
		strcpy(Path_Music, Path_Happy_New_Year);
		break;
	case 'D':
		strcpy(Path_Music, Path_Little_Star);
		break;
	case 'E':
		strcpy(Path_Music, Path_lyricwaltz);
		break;
	case 'F':
		strcpy(Path_Music, Path_Merry_Christmas);
		break;
	default:
		break;
	}
	//----------選擇關卡------------end
	initFile();	//讀入相關文件
	creatView();	//創建地圖
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GameControl, NULL, 0, NULL);	//啟動鍵盤控制線程

	GameView();	//繪圖
	getch();	//防止游戲結束后退出
	return 0;
}


void initFile()	//初始化文件資源
{
	FILE *fp = NULL;
	fp = fopen(Path_Music, "r");
	if (fp != NULL)
	{
		while (fscanf(fp, "%s", track[mLength]) != EOF)
		{	//讀入所有鍵
			mLength++;
		}
	}
	fclose(fp);
}

DWORD WINAPI ThreadDrawTrack()
{
	setrendermode(RENDER_MANUAL);
	int y = inter_y;
	int addh = 0;
	ThreadParameter tp;
	strcpy(tp.filename, Path_Music_Fail);
	for (; is_run(); delay_fps(FPS))
	{
		if (failplay)
		{
			cleardevice();
			sprintf(mCharsScore, "%d", mScore);
			setfont(100, 0, "宋體");
			setfontbkcolor(EGERGB(0xff, 0x00, 0x00));
			outtextxy(WINDOW_WIDTH / 2 + 20, WINDOW_HIGHT / 2, mCharsScore);
			outtextxy(0, WINDOW_HIGHT / 2, "得分:");
			break;
		}
		if (-1 * y >= 0)
		{
			break;
		}
		putimage(0, -1 * y, totalimg);
		y -= 2 + accelerate;
		below = addh - mScore*Blank_HIGHT - 160;
		if (below >= WINDOW_HIGHT)
		{
			CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
			failplay = true;
		}
		addh += 2;
		drawCountText();	//更新顯示得分
	}
	return 1;
}

void GameView()	//	繪圖
{
	randomize();	//初始化隨機數
	cleardevice();	//清屏
	setcaption("別踩白塊 - 1707004544 李浩");		//窗口標題
	setbkcolor(EGERGB(0xff, 0xff, 0xff));	//背景色

	ThreadDrawTrack();
}

DWORD WINAPI GameControl()	//控制
{
	int curr = 0;
	int k = 0, index = 0;
	ThreadParameter tp;
	strcpy(tp.filename, Path_Music_Fail);	//給線程傳參結構體賦值失敗音樂路徑
	for (; k != key_esc;)
	{
		if (kbhit())
		{
			k = getch();
			if (k == 'S')
			{
				if (randtrack[mLength - curr - 1] == 1)
				{
					music_play_control(track[index++]);		//將鍵音傳入音樂處理函數
					mScore++;	//得分加一
					drawCountText();	//刷新計分標簽
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);	//播放失敗音樂線程
					failplay = true;	//設定失敗標志為真
				}
			}
			else if (k == 'D')
			{
				if (randtrack[mLength - curr - 1] == 2)
				{
					music_play_control(track[index++]);
					mScore++;
					drawCountText();
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
					failplay = true;
				}
			}
			else if (k == '4')
			{
				if (randtrack[mLength - curr - 1] == 3)
				{
					music_play_control(track[index++]);
					mScore++;
					drawCountText();
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
					failplay = true;
				}
			}
			else if (k == '5')
			{
				if (randtrack[mLength - curr - 1] == 4)
				{
					music_play_control(track[index++]);
					mScore++;
					drawCountText();
				}
				else
				{
					CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tp, 0, NULL);
					failplay = true;
				}
			}
			curr++;
			accelerate = mScore / 25;	//設定滑塊每25分增加速度

		}
		if (failplay)
		{
			api_sleep(500);	//等待播放音樂線程啟動
			return 0;
		}
	}
	return 0;
}

void drawCountText()	//繪制計數器
{
	sprintf(mCharsScore, "%d", mScore);		//int轉char*
	setfont(40, 0, "宋體");		//設定字體樣式
	setfontbkcolor(EGERGB(0xff, 0x00, 0x00));	//設置字體背景色
	outtextxy(WINDOW_WIDTH / 2, 0, mCharsScore);	//在窗口輸出字體
}

void creatView()
{
	int point[8];	//四邊形點集
	int x = 0, y = 0;	//基定點坐標
	randomize();	//初始化隨機數
	totalimg = newimage(WINDOW_WIDTH, Blank_HIGHT*mLength);	//初始化總畫布
	setbkcolor(COLOR_WHITE, totalimg);	//設置總畫布背景
	cleardevice(totalimg);	//清空總畫布
	for (int i = 0; i < mLength; i++)
	{
		randtrack[i] = random(4) + 1;	//參數0-3的隨機數,加一后為軌道位置
		y = inter_y;
		x = (randtrack[i] - 1) * Blank_WIDTH;
		point[0] = x;
		point[1] = y;
		point[2] = x + Blank_WIDTH;
		point[3] = y;
		point[4] = x + Blank_WIDTH;
		point[5] = y + Blank_HIGHT;
		point[6] = x;
		point[7] = y + Blank_HIGHT;
		setfillstyle(SOLID_FILL, Blank_COLOR);	//設置方塊顏色
		fillpoly(4, point, totalimg);	//繪制方塊
		inter_y += Blank_HIGHT;	//設置滑塊公共高度
	}
}

void music_play_control(char key[])	//控制音樂播放
{
	int flage = 0;
	char copy[30];
	char buff[10][30];	//同時按下的鍵值
	char temp[200];
	memset(temp, '\0', sizeof(temp));
	//按照特定格式讀入每個鍵符
	if (key[0] != '(')
	{
		strcat(temp, Path_pre);
		strcat(temp, key);
		strcat(temp, Path_end);
		strcpy(tpp.filename, temp);
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tpp, 0, NULL);
	}
	else
	{
		memset(buff, '\0', sizeof(buff));
		strcpy(copy, key);
		for (int i = 0; i < strlen(copy); i++)
		{
			if (copy[i] == '.')
			{
				flage++;
				copy[i] = ' ';
			}
			else if (copy[i] == '(' || copy[i] == ')')
			{
				copy[i] = ' ';
			}
		}
		if (flage == 0)
		{
			sscanf(copy, "%s", buff[0]);
		}
		else if (flage == 1)
		{
			sscanf(copy, "%s %s", buff[0], buff[1]);
		}
		else if (flage == 2)
		{
			sscanf(copy, "%s %s %s", buff[0], buff[1], buff[2]);
		}
		else if (flage == 3)
		{
			sscanf(copy, "%s %s %s %s", buff[0], buff[1], buff[2], buff[3]);
		}
		for (int i = 0; i < flage; i++)
		{
			strcat(temp, Path_pre);
			strcat(temp, buff[i]);
			strcat(temp, Path_end);
			strcpy(tpp.filename, temp);
			CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)music_play, &tpp, 0, NULL);	//啟動音樂播放線程
			api_sleep(200);
		}
	}
}
DWORD WINAPI music_play(LPVOID pParam)	//播放音樂
{
	//mciSendString(tp->filename, NULL, 0, NULL); //低效播放方式

	//高效率無阻塞播放
	ThreadParameter *tp = (ThreadParameter *)pParam;
	MCI_OPEN_PARMS open;//定義打開結構體變量
	open.lpstrElementName = tp->filename;//填充參數
	mciSendCommand(0, MCI_OPEN, MCI_OPEN_ELEMENT, DWORD_PTR(&open));//打開
	MCI_PLAY_PARMS play;//定義播放結構題變量
	mciSendCommand(open.wDeviceID, MCI_PLAY, 0, DWORD_PTR(&play));//非阻塞式播放
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM