C/C++實現flappy bird圖形版

前言

除了利用控制檯打印字符,我們還可以用圖片、音樂等素材,開發出更加精彩的遊戲。

點子

這裏開發出圖形版的flappy bird。

具體實現

1. 導入圖片背景和小鳥

使用loadimage和putimage,導入背景圖和小鳥圖片。

其中,文件名路徑都是LPCTSTR,這是什麼類型?簡單說,這是一個字符串指針。原因在於用路徑中可能有英文字符,也可能有中文字符,中文字符使用兩個字節作爲一個字符(漢字機內碼)。官方文檔明確提及了這個問題:

中文版錯誤提示如下:error C2665: “outtextxy”: 2 個重載中沒有一個可以轉換所有參數類型
英文版錯誤提示如下:error C2665: ‘outtextxy’ : none of the 2 overloads could convert all the argument types
同樣的,對於其他一些包含字符串調用的函數,例如 loadimage、drawtext 等,也會遇到類似問題。
【錯誤原因】
簡單來說,這是由於字符編碼問題引起的。VC6 默認使用的 MBCS 編碼,而 VC2008 及高版本 VC 默認使用的 Unicode 編碼。以下詳細解釋這個問題:
用 char 表示字符時,英文佔用一個字節,中文站用兩個字節。這樣有一個嚴重的問題:兩個連續字節,究竟是兩個英文字符,還是一箇中文字符?爲了解決這個問題,Unicode 編碼誕生了。Unicode 編碼不管中文英文都用兩個字節表示。
對於 MBCS 編碼,字符變量用 char 定義。對於 Unicode 編碼中,字符變量用 wchar_t 定義。爲了提高代碼的自適應性,微軟在 tchar.h 裏面定義了 TCHAR,而 TCHAR 會根據項目定義的編碼,自動展開爲 char 或 wchar_t。
在 Windows API 和 EasyX 裏面的大多數字符串指針都用的 LPCTSTR 或 LPTSTR 類型,LPCTSTR / LPTSTR 就是“Long Point (Const) Tchar STRing”的縮寫。所以可以認爲,LPCTSTR 就是const TCHAR *,LPTSTR 就是TCHAR *。
於是,在 VS2008 裏面,給函數傳遞 char 字符串時,就會提示前述錯誤。

爲了讓代碼適應char和wchar_t兩種情況,可以用TCHAR宏,這個宏當項目定義爲unicode時表示wchar_t,否則表示char。
對應的,還有 _T("") 宏用來表示字符串。

#include <easyx.h>
#include <conio.h>

int main() {
	initgraph(350, 600);
	IMAGE img_bk, img_bird;
	loadimage(&img_bk, _T("..\\source\\background.jpg"));
	putimage(0, 0, &img_bk);

	loadimage(&img_bird, _T("..\\source\\bird2.jpg"));
	putimage(100, 200, &img_bird);
	_getch();
	closegraph();
	return 0;
}

效果:
在這裏插入圖片描述

2. 用遮罩圖解決小鳥邊框

小鳥的圖片不是透明的,帶有明顯的白色邊框,因此,我們需要利用小鳥的遮罩圖bird1.jpg,它與bird2.jpg像素一一對應,bird1中白色的像素區域將bird2中對應的像素顯示,bird1中黑色的區域將bird2中對應的像素隱藏。對應的遮罩圖可以通過PS摳圖生成。

這裏必須瞭解三元光柵操作碼。看文檔就可以了。

#include <easyx.h>
#include <conio.h>

int main() {
	initgraph(350, 600);
	IMAGE img_bk;
	loadimage(&img_bk, _T("..\\source\\background.jpg"));
	putimage(0, 0, &img_bk);

	IMAGE img_bd1, img_bd2;
	loadimage(&img_bd1, _T("..\\source\\bird1.jpg"));
	loadimage(&img_bd2, _T("..\\source\\bird2.jpg"));
	
	//putimage(100, 200, &img_bd1, DSTINVERT); //繪製出的像素顏色 = NOT 屏幕顏色
    putimage(100, 200, &img_bd1, NOTSRCERASE); //繪製出的像素顏色 = NOT (屏幕顏色 OR 圖像顏色)

	//putimage(100, 200, &img_bd2, SRCAND); //繪製出的像素顏色 = 屏幕顏色 AND 圖像顏色
	//putimage(100, 200, &img_bd2, SRCCOPY); //繪製出的像素顏色 = 圖像顏色
	//putimage(100, 200, &img_bd2, SRCERASE); //繪製出的像素顏色 = (NOT 屏幕顏色) AND 圖像顏色
	putimage(100, 200, &img_bd2, SRCINVERT); //繪製出的像素顏色 = 屏幕顏色 XOR 圖像顏色
	//putimage(100, 200, &img_bd2, SRCPAINT); //繪製出的像素顏色 = 屏幕顏色 OR 圖像顏色

	_getch();
	closegraph();
	return 0;
}

在這裏插入圖片描述

3. 用遊戲框架重構flappy bird遊戲

將前面的字符版flappy bird的遊戲框架搬過來,讓小鳥自由下落和操作上升:

#include <easyx.h>
#include <conio.h>
#include <windows.h>

IMAGE img_bk, img_bd1, img_bd2, img_bar_up1, img_bar_up2, img_bar_down1, img_bar_down2;
int bird_x, bird_y;

void initgame() {
	initgraph(350, 600);
	loadimage(&img_bk, _T("..\\source\\background.jpg")); //350 x 600
	loadimage(&img_bd1, _T("..\\source\\bird1.jpg")); //34 x 24
	loadimage(&img_bd2, _T("..\\source\\bird2.jpg"));
	loadimage(&img_bar_up1, _T("..\\source\\bar_up1.gif")); //140 x 600
	loadimage(&img_bar_up2, _T("..\\source\\bar_up2.gif"));
	loadimage(&img_bar_down1, _T("..\\source\\bar_down1.gif"));
	loadimage(&img_bar_down2, _T("..\\source\\bar_down2.gif"));

	bird_x = 50, bird_y = 200;
	BeginBatchDraw();
}

void show() {
	//顯示背景
	putimage(0, 0, &img_bk);
	//顯示小鳥
    putimage(bird_x, bird_y, &img_bd1, NOTSRCERASE); //繪製出的像素顏色 = NOT (屏幕顏色 OR 圖像顏色)
	putimage(bird_x, bird_y, &img_bd2, SRCINVERT); //(XOR)方式 繪製出的像素顏色 = 屏幕顏色 XOR 圖像顏色
	//顯示上面的障礙物
	putimage(150, -300, &img_bar_up1, NOTSRCERASE);
	putimage(150, -300, &img_bar_up2, SRCINVERT); 
	//顯示下面的障礙物
	putimage(150, 400, &img_bar_down1, NOTSRCERASE);
	putimage(150, 400, &img_bar_down2, SRCINVERT);

	FlushBatchDraw();
}

void updateWithoutInput() {
	if (bird_y < 570)  bird_y += 5;
	Sleep(150);  //減緩小鳥的下落速度和柱子移動速度
}

void updateWithInput() {
	char input;
	if (_kbhit()) {
		input = _getch();
		if (input == ' ' && bird_y > 20) bird_y -= 30;
	}
}

void gameover() {
	EndBatchDraw();
	_getch();
	closegraph();
}

int main() {
	initgame();
	while (1) {
		show();
		updateWithInput();
		updateWithoutInput();
	}
	return 0;
}

在這裏插入圖片描述

4. 導入聲音

播放音樂需要用mciSendString函數。需要先打開(open)音樂,再就可以循環(play … repeat)播放、僅播放一次(play)、關閉(close)前面的音樂。

#pragma comment(lib, "Winmm.lib")

void initgame() {
	...
	mciSendString(_T("open ..\\source\\background.mp3 alias bk_music"), NULL, 0, NULL); //打開音樂
	mciSendString(_T("play bk_music repeat"), NULL, 0, NULL);  //循環播放音樂
	...
}

void updateWithInput() {
	char input;
	if (_kbhit()) {
		input = _getch();
		if (input == ' ' && bird_y > 20) {
			bird_y -= 30;
			mciSendString(_T("close jpmusic"), NULL, 0, NULL);  //先把前面一次的音樂關閉
			//打開音樂
			mciSendString(_T("open ..\\source\\Jump.mp3 alias jpmusic"), NULL, 0, NULL); 
			mciSendString(_T("play jpmusic"), NULL, 0, NULL);     //僅播放一次
		}
	}
}
...

5. 實現柱子的移動和碰撞檢測

這裏需要範圍檢查,不僅僅是像字符版那樣。

#include <easyx.h>
#include <conio.h>
#include <string>
#include <windows.h>
#pragma comment(lib, "Winmm.lib")
using namespace std;

IMAGE img_bk, img_bd1, img_bd2, img_bar_up1, img_bar_up2, img_bar_down1, img_bar_down2;
int bird_x, bird_y;
int bar_yTop, bar_yDown, bar_x; //障礙物座標
HWND hwnd;
//int score;           //分數
int exitflag = 0; //退出標記

void initgame() {
	initgraph(400, 600);
	loadimage(&img_bk, _T("..\\source\\background.jpg"), 400, 600, false); //350 x 600
	loadimage(&img_bd1, _T("..\\source\\bird1.jpg")); //34 x 24
	loadimage(&img_bd2, _T("..\\source\\bird2.jpg"));
	loadimage(&img_bar_up1, _T("..\\source\\bar_up1.gif")); //140 x 600
	loadimage(&img_bar_up2, _T("..\\source\\bar_up2.gif"));
	loadimage(&img_bar_down1, _T("..\\source\\bar_down1.gif"));
	loadimage(&img_bar_down2, _T("..\\source\\bar_down2.gif"));

	mciSendString(_T("open ..\\source\\background.mp3 alias bk_music"), NULL, 0, NULL); //打開音樂
	mciSendString(_T("play bk_music repeat"), NULL, 0, NULL);  //循環播放音樂

	hwnd = GetHWnd();
	bird_x = 50, bird_y = 250;
	bar_yTop = 300, bar_yDown = 400, bar_x = 220;
	//score = 0;
	BeginBatchDraw();
}

void show() {
	//顯示背景
	putimage(0, 0, &img_bk);
	//顯示小鳥
    putimage(bird_x, bird_y, &img_bd1, NOTSRCERASE); //繪製出的像素顏色 = NOT (屏幕顏色 OR 圖像顏色)
	putimage(bird_x, bird_y, &img_bd2, SRCINVERT); //(XOR)方式 繪製出的像素顏色 = 屏幕顏色 XOR 圖像顏色

	//outtextxy(200, 350, _T("score"));
	//顯示上面的障礙物
	putimage(bar_x, -300, &img_bar_up1, NOTSRCERASE);
	putimage(bar_x, -300, &img_bar_up2, SRCINVERT);
	//顯示下面的障礙物
	putimage(bar_x, bar_yDown, &img_bar_down1, NOTSRCERASE);
	putimage(bar_x, bar_yDown, &img_bar_down2, SRCINVERT);

	FlushBatchDraw();
}

void updateWithoutInput() {
	bar_x -= 5;
	if (bar_x + 140 <= 0) bar_x = 300; //重新出現柱子
	if (bird_x >= bar_x && bird_x <= bar_x + 140) { //判斷是否相撞
		if (bird_y <= bar_yTop || bird_y >= bar_yDown) { 
			MessageBox(hwnd, TEXT("勝敗乃兵家常事,少俠請重新再來!"), _T("結果"), MB_OK);
			system("pause");
			exitflag = 1;  
		 } //else score++;
	}
	if (bird_y < 570)  bird_y += 5; //小鳥自己下落
	Sleep(150);  //減緩小鳥的下落速度和柱子移動速度
}

void updateWithInput() {
	char input;
	if (_kbhit()) {
		input = _getch();
		if (input == ' ' && bird_y > 20) {
			bird_y -= 20;
			mciSendString(_T("close jpmusic"), NULL, 0, NULL);  //先把前面一次的音樂關閉
			mciSendString(_T("open ..\\source\\Jump.mp3 alias jpmusic"), NULL, 0, NULL); //打開音樂
			mciSendString(_T("play jpmusic"), NULL, 0, NULL);     //僅播放一次
		}
	}
}

void gameover() {
	EndBatchDraw();
	_getch();
	closegraph();
}

int main() {
	initgame();
	while (1) {
		show();
		updateWithoutInput();
		updateWithInput(); 
		if (exitflag) break;
	}
	gameover();
	return 0;
}

效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章