前言
除了利用控制檯打印字符,我們還可以用圖片、音樂等素材,開發出更加精彩的遊戲。
點子
這裏開發出圖形版的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;
}
效果: