直接上代碼
1.首先是頭文件編寫
#include <iostream>
#include <graphics.h>#include <string>
2,添加畫布
長1280,寬720
initgraph(1280, 720);
?3.添加主循環
?bool running = true;
while(runing)
{
????????
}
?4.定義結構體變量msg
ExMessge msg;
5.開啟渲染緩沖區
?BeginBatchDraw();//開啟第二張畫布
?cleardevice();//清屏
?FlushBatchDraw();//在第二張畫布里畫畫?EndBatchDraw();//切換畫布?
6.按鍵或鼠標移動檢測
peekmessage是WINDOWS自帶的API函數,它可以檢測外部輸入設備的狀態,比如你鼠標動一下,鍵盤按一下,它都能檢測得到,然后將信號賦值給第一個參數結構體。
?while (peekmessage(&msg))//循環檢測按鍵或者鼠標移動
?{?}
7.減少內存占用
DWORD start_time = GetTickCount();//記錄下開始的時間
DWORD end_time = GetTickCount();//記錄下結束的時間
DWORD delta_time = end_time - start_time;//記錄下循環一次的時間
if (delta_time < 1000 / 144)//用來實現幀數,為1s投放144張畫面,計算差值,用來彌補時間
{
? ? Sleep(1000 / 114 - delta_time);
}
然后是總的初始狀態設置。
#include <iostream>
#include <graphics.h>
int main()
{initgraph(1280, 720);bool running = true;ExMessage msg;BeginBatchDraw();//開啟第二張畫布while (running){DWORD start_time = GetTickCount();while (peekmessage(&msg)){}cleardevice();//清屏FlushBatchDraw();//在第二張畫布里畫畫DWORD end_time = GetTickCount();DWORD delta_time = end_time - start_time;if (delta_time < 1000 / 144){Sleep(1000 / 114 - delta_time);}}EndBatchDraw();//切換畫布return 0;
}
畫圖在哪里畫?
一定要放在清屏函數后,和調用畫布前。
正式開始項目
1.加載資源。將素材圖片放到項目的工程目錄下面,這里也是項目的根目錄
2.放置圖片,這個函數放在clear的后面,開啟第二張畫布的前面
?IMAGE img_background;//定義圖片
loadimage(&img_background, _T("img/background.png"));//加載圖片
第一個參數是圖片的地址,第二個是圖片在工程絕對路徑,以工程路徑為根路徑,在img文件下。
putimage(0, 0, &img_background);//放置圖片
如何讓動畫動起來?
角色動畫分為兩類,一種是序列幀動畫,一種是關鍵幀動畫
序列幀動畫由一組圖片構成,通過不斷切換動畫來實現動起來,借助視覺暫留效應。
關鍵幀動畫就是通過幾個點動來襯托整個物體動起來。
首先是初始化
int idx_current_anim;//用來記錄索引,實現循環播放一組圖片。
const int PLAYER_ANIM_NUM = 6;//記錄一組圖片的最大值IMAGE img_player_left[PLAYER_ANIM_NUM];//用來記錄一組圖片
加載圖片,告訴編譯器圖片的位置。 wstring 是拼接函數。loadimage是加載圖片的函數。
void LoadAnimation()
{for (size_t i = 0; i < PLAYER_ANIM_NUM; i++){wstring path = L"img/player_left_" + to_wstring(i) + L".png";loadimage(&img_player_left[i], path.c_str());}
}
static int counter = 0;//記錄游戲幀
if(++counter%5==0)//實現執行5次循環就切換下一張圖片
idx_current_anim++;
?//循環播放操作,PLAYER_ANIM_NUM=6,idx_current_anim=6時,將其賦值為0,相當于重新賦值為0,和if -else類似。
?idx_current_anim = idx_current_anim % PLAYER_ANIM_NUM;等價于
? ? ? ? if (idx_current_anim == 6)
? ? ? ? {
? ? ? ? ? ? idx_current_anim = 0;
? ? ? ? }
初始化加載
記住,初始化動畫加載函數后一定要放在主函數里面調用!?
動畫周圍正方形多余部分
圖中,動畫圖與背景圖格格不入。因為人物周圍的白框太出戲了。
這里引用的是博主“晚晶”的代碼。文章鏈接如下:
EasyX圖片透明度
void putpicture(int dstx, int dsty, IMAGE* img, COLORREF color, int alpha) {//0~255 255表示不透明
?? ?DWORD* imgp = GetImageBuffer(img);
?? ?DWORD* bgimgp = GetImageBuffer();
?? ?int w, bw, h, i, j;
?? ?w = img->getwidth();
?? ?bw = getwidth();
?? ?h = img->getheight();
?? ?color += 0xff000000;
?? ?if (alpha < 0)alpha = 0;
?? ?else if (alpha > 255)alpha = 255;
?? ?for (i = 0; i < h; ++i)
?? ??? ?for (j = 0; j < w; ++j)
?? ??? ??? ?if (imgp[i * w + j] != color)
?? ??? ??? ??? ?bgimgp[(i + dsty) * bw + j + dstx] = RGB(
?? ??? ??? ??? ??? ?((int)(alpha / 255.0 * GetRValue(imgp[i * w + j]) + (1 - alpha / 255.0) * GetRValue(bgimgp[(i + dsty) * bw + j + dstx]))),
?? ??? ??? ??? ??? ?((int)(alpha / 255.0 * GetGValue(imgp[i * w + j]) + (1 - alpha / 255.0) * GetGValue(bgimgp[(i + dsty) * bw + j + dstx]))),
?? ??? ??? ??? ??? ?((int)(alpha / 255.0 * GetBValue(imgp[i * w + j]) + (1 - alpha / 255.0) * GetBValue(bgimgp[(i + dsty) * bw + j + dstx])))
?? ??? ??? ??? ?);
}第一個參數是x,第二個參數是y坐標,第三個參數圖片應用地址,第四個參數是RGB值。第五個是透明度大小(0-255,255是完全不透明)
將這段代碼進行調用,然后用putimage展示背景圖,博主代碼用來實現透明度圖片,這里的背景圖片最好是黑色更好,白色不好搞。
?? ??? ?putimage(0, 0, &img_background);//放置圖片
?? ??? ?for (int toumdu = 0; toumdu < 255; toumdu++)
?? ??? ?{
?? ??? ??? ?putpicture(100, 50, &img_player[idx_current_anim], RGB(255, 255, 255), toumdu);//放置圖片
?? ??? ?}
?? ??? ?
第二種方法:
//設置透明度
#pragma comment(lib,"MSIMG32.LIB")
inline void putimage_alpha(int x, int y, IMAGE* img)
{
? ? int w = img->getwidth();
? ? int h = img->getheight();
? ? AlphaBlend(GetImageHDC(NULL), x, y, w, h, GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}
?putimage_alpha(500, 500, &img_player_left[idx_current_anim]);
直接使用這個作為渲染
控制角色移動
首先創建一個角色初始位置。
POINT player_pos = { 500,500 };?
移動情況
? ? ? ? ? ? if (msg.message == WM_KEYDOWN)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? switch (msg.vkcode)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? case VK_UP:
? ? ? ? ? ? ? ? ? ? player_pos.y -= PLAYER_SPEED; break;
? ? ? ? ? ? ? ? case VK_DOWN:
? ? ? ? ? ? ? ? ? ? player_pos.y += PLAYER_SPEED; break;
? ? ? ? ? ? ? ? case VK_LEFT:
? ? ? ? ? ? ? ? ? ? player_pos.x -= PLAYER_SPEED; break;
? ? ? ? ? ? ? ? case VK_RIGHT:
? ? ? ? ? ? ? ? ? ? player_pos.x += PLAYER_SPEED; break;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
定義移動速度
int PLAYER_SPEED=10;//玩家速度,其實就是一次位置移動大小
設置邊界,只能在指定范圍內移動,超過則原地不動
y的范圍是30<y<600? ? ? ? ? ? ? ??
if (player_pos.y > 600)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? player_pos.y = 600;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (player_pos.y < 30)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? player_pos.y = 30;
? ? ? ? ? ? ? ? }x的范圍是30<x<1200
? ? ? ? ? ? ? ? if (player_pos.x > 1200)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? player_pos.x = 1200;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (player_pos.x < 30)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? player_pos.x = 30;
? ? ? ? ? ? ? ? }
移動頓挫感
每當我們長按一個移動鍵移動角色的過程中,角色會先動一下,然后,再連續移動。這是因為我們按下一個鍵后,WIN會記下一個消息,但如果是連續按下的話,就會等待一段時間后才連續記錄消息。這是系統的原因,比如我們在打字過程中,長按一個j鍵,也是先顯示一個j,然后才突然顯示很多個j
解決辦法
改變游戲人物的移動邏輯,之前是按下哪個鍵,就怎樣怎樣,松開之后就不執行了,只是檢測有沒有按下。
現在的邏輯是:按下某個鍵,移動,松開某個鍵,停止移動,同時檢測按鍵的按下,松開。
設置4個bool狀態,分別表示上下左右,初始值為false
?? ?bool is_move_up = false;
?? ?bool is_move_down = false;
?? ?bool is_move_left = false;
?? ?bool is_move_right = false;
?按鍵循環采集采用if -if
while (peekmessage(&msg))//獲取一個消息
{
?? ?//按鍵被按下
?? ?if (msg.message == WM_KEYDOWN)
?? ?{
?? ??? ?cout << "報告!按鍵被按下,請求指示!" << endl;
?? ??? ?switch (msg.vkcode)
?? ??? ?{
?? ??? ?case 0x57:is_move_up=true; cout << "向上移動" << endl;; break;
?? ??? ?case 0x53:is_move_down=true; cout << "向下移動" << endl; break;
?? ??? ?case 0x44:is_move_right =true; cout << "向右移動" << endl; break;
?? ??? ?case 0x41:is_move_left=true; cout << "向左移動" << endl; break;
?? ??? ?}
?? ?}
?? ?else if (msg.message == WM_KEYUP)
?? ?{
?? ??? ?cout << "按鍵彈起,請求指示!" << endl;
?? ??? ?switch (msg.vkcode)
?? ??? ?{
?? ??? ?case 0x57:is_move_up = false; cout << "向上停止" << endl;; break;
?? ??? ?case 0x53:is_move_down = false; cout << "向下停止" << endl; break;
?? ??? ?case 0x44:is_move_right = false; cout << "向右停止" << endl; break;
?? ??? ?case 0x41:is_move_left = false; cout << "向左停止" << endl; break;
?? ??? ?}
?? ?}
然后再設置true的情況
/*野豬移動狀態函數塊*/
{
?? ?if (is_move_up) player_pos.y -= MOTION_SPEED;
?? ?if (is_move_down) player_pos.y += MOTION_SPEED;
?? ?if (is_move_right) player_pos.x += MOTION_SPEED;
?? ?if (is_move_left) player_pos.x -= MOTION_SPEED;
?? ?putimage_alpha(player_pos.x, player_pos.y, &ENEMY_LEFT[idx_current_anim]);
}
關于沖突按鍵ctrl
按下W S A 會間接觸發“按下ctrl鍵”,所以在使用按鍵定義時一定要注意,不要在ctrl判斷里面加循環,或者system("pause"),system("cls")。
面向對象編程
首先引入vector容器
創建類和容器
class Animation
{
public:Animation(){}~Animation(){}
private:int interval_ms=0;vector<IMAGE*>frame_list;
};
把加載圖片資源的部分放在構造 函數里面
編寫加載圖片的函數在構造函數里面
Animation(LPCTSTR path,int num,int interval){interval_ms = interval;TCHAR path_file[256];for (size_t i = 0; i < num; i++){_stprintf_s(path_file, path, i);IMAGE* frame = new IMAGE();loadimage(frame, path_file);frame_list.push_back(frame);//添加到vector容器中}}
記住要釋放內存。在析構函數中實現
for (size_t i = 0; i < frame_list.size(); i++)delete frame_list[i];//釋放vectro容器的所有值
編寫幀索引更新和渲染的代碼
用計數器和計時器來控制動畫的幀更新有什么區別?
計數器的幀更新略微不穩定有時候1幀的時間長,有時短,但是計時器控制的幀是肯定比計數器穩定
void Play(int x, int y, int delta)
{timer += delta;if (timer >= interval_ms){idx_frame = (idx_frame + 1) % frame_list.size();timer = 0;}
}在private中定義
private:int idx_frame = 0;//動畫幀索引int timer = 0;//動畫計時器
初始化
Animation anim_left_player(_T("img/player_left_%d.png"), 6, 45);//初始化左邊圖片Animation anim_right_player(_T("img/player_right_%d.png"), 6, 45);//初始化右邊圖片
翻轉角色
void DrawPlayer(int delta, int dir_x)
{static bool facing_left = false;if (dir_x < 0)facing_left = true;else if (dir_x > 0)facing_left = false;if (facing_left)anim_left_player.Play(player_pos.x, player_pos.y, delta);else anim_right_player.Play(player_pos.x, player_pos.y, delta);
}
添加陰影
IMAGE img_shadow;//初始化玩家腳下的陰影
loadimage(&img_shadow, _T("img/shadow_player.png"));
定義陰影三度
const int PLAYER_WIDTH = 80;//玩家寬度
const int PLAYER_HEIGHT = 80;//玩家高度
const int SHADOW_WIDTH = 32;//陰影寬度
計算陰影坐標,代碼放到翻轉函數中
int pos_shadow_x = player_pos.x + (PLAYER_WIDTH / 2 - SHADOW_WIDTH / 2);//計算陰影的xint pos_shadow_y = player_pos.y + PLAYER_HEIGHT - 8;//計算陰影的y
putimage_alpha(pos_shadow_x, pos_shadow_y, &img_shadow);
然后開始測試
把代碼放進循環中
DrawPlayer(1000 / 100, Go_right-Go_left);
可以發現一個問題,就是玩家在走斜線時移動速度比左右上下快不少
使用代碼
//防止往斜上方走速度過快int dir_x = Go_right - Go_left;int dir_y = Go_down-Go_up;double len_dir = sqrt(dir_x * dir_x + dir_y * dir_y);if (len_dir != 0){double normalized_x = dir_x / len_dir;double normalized_y = dir_y / len_dir;player_pos.x += (int)(Player_speed * normalized_x);player_pos.y += (int)(Player_speed * normalized_y);}
限制玩家區域