《C++遊戲開發》筆記十四 平滑過渡的戰爭迷霧(二) 實現:真正的迷霧來了
本系列文章由七十一霧央編寫,轉載請註明出處。
這兩天不少朋友留言提出了一些問題,但是由於霧央家裡網路出了點問題,所以這兩天都上不了網,沒有及時回答大家,關注了霧央微博的朋友就知道這件事,抱歉了。
另外,歡迎轉載文章,霧央會把它當成對自己的認可~(@^_^@)~,但是請不要刪除第一段話或者註明一下原文地址,好嗎?請尊重一下作者的勞動。
一、原理回顧
今天繼續來說戰爭迷霧,上一節介紹了一下戰爭迷霧的原理,不知道大家清楚了沒?如果沒清楚,也不要緊,現在再來囉嗦幾句哈。
我們的素材是下面這張圖
我們還是圖解吧,這樣應該更形象,先給它編上號。
用滑鼠點選一下,散開一片迷霧,大家可以看到上面標示的數字,左上角是4,右上角是8,左下角是1,右下角是2
在右邊再點一下,我們可以看到兩片迷霧疊加起來了,過渡的很自然。大家注意一下數字,兩片迷霧中間的數字變成了12=4+8,3=2+1
繼續點,同理
看了上面的圖,大家應該清楚了吧,霧央假定大家都清楚了~(@^_^@)~,如果有問題的朋友可以留言或者微博@七十一霧央。
我們每次點選遊戲視窗的時候,驅散一個圓形的迷霧,這個圓形就只需要1+2+4+8號圖元拼接起來就可以了,當同一個Tile內有多個圖元時,將它們的數字相加,用新數字的圖元替換掉即可。
二、實現步驟
我們知道,把上面的滑鼠換成人物,就可以營造出遊戲中的戰爭迷霧效果:隨著人物的走動,迷霧散開,合理的方式應該是以人物為中心散開迷霧,就像魔獸那樣。但是霧央簡化了一下問題,採用的是以滑鼠為左上角散開迷霧。以滑鼠為中心散開留給大家完成,也就是加個判斷,找出滑鼠附近的四個方塊。
如果大家看過了上上一節,即戰爭迷霧的初步實現,那麼就容易多了,因為區別只存在於兩個地方:繪圖函式和更新函式。
這次霧央用了一個大一點的地圖1280*640,為了多點幾下,呵呵。我們的圖元方塊是128*128的,那麼網格就是10*5個。
現在大家都清楚了每個網格要貼它的數字的圖,那麼我們怎麼找到數字為n的圖元的起始座標呢?
大家觀察一下下面的圖,找找規律
大家發現了沒有?每一列的數字除以4得到的商是相同的,分別為0,1,2,3;每一行的數字對4取餘得到的結果也是相同的,分別是0,1,2,3!
那麼問題就簡單了,編號為n的圖元的起始座標
x=(n/4)*128,
y=(n%4)*128
那麼我們繪製戰爭迷霧的函式就可以修改成下面這樣了
//繪製戰爭迷霧
void CScene::DrawFog(CDC &cDC)
{
for(int i=0;i<10;i++)
for(int j=0;j<5;j++)
m_black[m_mode].Draw(cDC,i*128,j*128,128,128,(m_fogArray[i][j]/4)*128,(m_fogArray[i][j]%4)*128,128,128);
}
接下來要處理的就是更新迷霧區域的函數了
我們首先計算出滑鼠點選的格子編號
//首先計算出滑鼠所在的格子
int xPosBox=x/128;
int yPosBox=y/128;
如果這個格子沒有被點選過,那麼就展開迷霧,並進行數值疊加,注意如果數字達到了15以上,就保持15,因為15已經是全開的狀態了,在上一節霧央提過,如果是用於地形拼接的話,那麼就可以在幾種鋪滿狀態的草地圖案隨機選擇,造成豐富的地形效果。另外,霧央偷懶了,沒有進行陣列邊界判斷!但是呢,為了防止陣列越界導致的錯誤,霧央就把陣列擴大了一點,變成11*6的陣列,這樣就不會有越界錯誤了。
void Add(int fogArray[][6],int i,int j,int num)
{
fogArray[i][j]+=num;
if(fogArray[i][j]>15)
fogArray[i][j]=15;
}
//更新迷霧區域
void CScene::UpdateFogArea(int x,int y)
{
//首先計算出滑鼠所在的格子
int xPosBox=x/128;
int yPosBox=y/128;
if(m_clickArray[xPosBox][yPosBox]==0)
{
//左上+4,右上+8,左下+1,右下+2
Add(m_fogArray,xPosBox,yPosBox,4);
Add(m_fogArray,xPosBox+1,yPosBox,8);
Add(m_fogArray,xPosBox,yPosBox+1,1);
Add(m_fogArray,xPosBox+1,yPosBox+1,2);
//點過的地方已經散開過一次了,就不再疊加
m_clickArray[xPosBox][yPosBox]=1;
}
}
另外,為了幫助大家理解,霧央設定了兩種模式,一種是顯示出迷霧狀態,另一種會多顯示出每塊迷霧圖元的編號數字,按‘Q’鍵可以在兩種狀態之間切換。
void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar=='Q' || nChar=='q')
m_scene->ChangeMode();
}
大家可以直接在圖元上輸出數字,霧央是乾脆直接用了兩張圖,一張帶數字,一張不帶,呵呵。0和1表示兩種模式,那麼在0和1之間切換,就和1異或就好了,0^1=1,1^1=0。
void CScene::ChangeMode()
{
m_mode^=1;
}
場景類現在的程式碼就如同下面這樣
標頭檔案
class CScene
{
private:
CImage m_bg; //背景圖片
CImage m_black[2];
int m_mode; //顯示模式
//每塊迷霧大小為128*128,對於1280*640的視窗即有10*5個小迷霧塊組成
int m_fogArray[11][6];
//每塊是否被點選過
int m_clickArray[11][6];
public:
CScene(char *bg);
~CScene();
public:
void ChangeMode();
//繪製背景
void DrawBG(CDC &cDC);
//繪製迷霧
void DrawFog(CDC &cDC);
//更新迷霧區域
void UpdateFogArea(int x,int y);
};
實現檔案
CScene::~CScene()
{
m_bg.Destroy();
m_black[0].Destroy();
m_black[1].Destroy();
}
CScene::CScene(char *bg)
{
m_bg.Load(bg);
m_black[0].Load("fog.png");
m_black[1].Load("fog2.png");
m_mode=0;
//將陣列清0,0表示為黑色迷霧狀態
memset(m_fogArray,0,sizeof(m_fogArray));
memset(m_clickArray,0,sizeof(m_clickArray));
}
//繪製背景
void CScene::DrawBG(CDC &cDC)
{
m_bg.Draw(cDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
}
//繪製戰爭迷霧
void CScene::DrawFog(CDC &cDC)
{
for(int i=0;i<10;i++)
for(int j=0;j<5;j++)
m_black[m_mode].Draw(cDC,i*128,j*128,128,128,(m_fogArray[i][j]/4)*128,(m_fogArray[i][j]%4)*128,128,128);
}
void Add(int fogArray[][6],int i,int j,int num)
{
fogArray[i][j]+=num;
if(fogArray[i][j]>15)
fogArray[i][j]=15;
}
//更新迷霧區域
void CScene::UpdateFogArea(int x,int y)
{
//首先計算出滑鼠所在的格子
int xPosBox=x/128;
int yPosBox=y/128;
if(m_clickArray[xPosBox][yPosBox]==0)
{
//左上+4,右上+8,左下+1,右下+2
Add(m_fogArray,xPosBox,yPosBox,4);
Add(m_fogArray,xPosBox+1,yPosBox,8);
Add(m_fogArray,xPosBox,yPosBox+1,1);
Add(m_fogArray,xPosBox+1,yPosBox+1,2);
//點過的地方已經散開過一次了,就不再疊加
m_clickArray[xPosBox][yPosBox]=1;
}
}
void CScene::ChangeMode()
{
m_mode^=1;
}
PS:霧央之前使用CImage貼圖的時候一直沒有主動釋放資源,最近才發現這個問題,對不住大家了。大家在解構函式裡都加上Destroy函式釋放一下資源,要不然會產生記憶體洩露。
執行看到的效果就是下面這樣,看起來還可以吧,哈哈
三、帶詳細註釋的原始碼
標頭檔案
// ChildView.h : CChildView 類的介面
//
#pragma once
#include "particle.h"
#include "scene.h"
// CChildView 視窗
class CChildView : public CWnd
{
// 構造
public:
CChildView();
// 特性
public:
//儲存客戶區大小
CRect m_client;
//雪花
CParticle *m_snow;
//場景
CScene *m_scene;
//緩衝DC
CDC m_cacheDC;
//緩衝點陣圖
CBitmap m_cacheCBitmap;
// 操作
public:
// 重寫
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
// 實現
public:
virtual ~CChildView();
// 生成的訊息對映函式
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
};
CPP
//-----------------------------------【程式說明】----------------------------------------------
// 【MFC遊戲開發】筆記十四 戰爭迷霧 配套原始碼
// VS2010環境
// 更多內容請訪問霧央CSDN部落格 http://blog.csdn.net/u011371356/article/category/1497651
// 霧央的新浪微博: @七十一霧央
//------------------------------------------------------------------------------------------------
// ChildView.cpp : CChildView 類的實現
//
#include "stdafx.h"
#include "GameMFC.h"
#include "ChildView.h"
#include "mmsystem.h"
#pragma comment(lib,"winmm.lib")//匯入聲音標頭檔案庫
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CChildView
CChildView::CChildView()
{
}
CChildView::~CChildView()
{
mciSendString("stop bgMusic ",NULL,0,NULL);
delete m_snow;
delete m_scene;
}
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_WM_TIMER()
ON_WM_CREATE()
ON_WM_LBUTTONDOWN()
ON_WM_KEYDOWN()
END_MESSAGE_MAP()
// CChildView 訊息處理程式
BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL);
//-----------------------------------遊戲資料初始化部分-------------------------
//開啟音樂檔案
mciSendString("open background.mp3 alias bgMusic ", NULL, 0, NULL);
mciSendString("play bgMusic repeat", NULL, 0, NULL);
//雪花
m_snow=new CParticle(100);
m_snow->Init();
//場景
m_scene=new CScene("bg.png");
return TRUE;
}
void CChildView::OnPaint()
{
static float lastTime=timeGetTime();
static float currentTime=timeGetTime();
//獲取視窗DC指標
CDC *cDC=this->GetDC();
//獲取視窗大小
GetClientRect(&m_client);
//建立緩衝DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap);
//————————————————————開始繪製——————————————————————
//貼背景,現在貼圖就是貼在緩衝DC:m_cache中了
m_scene->DrawBG(m_cacheDC);
//貼雪花
m_snow->Draw(m_cacheDC);
//更新雪花
currentTime=timeGetTime();
m_snow->Update(currentTime-lastTime);
lastTime=currentTime;
//畫出戰爭迷霧
m_scene->DrawFog(m_cacheDC);
//最後將緩衝DC內容輸出到視窗DC中
cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY);
//————————————————————繪製結束—————————————————————
//在繪製完圖後,使視窗區有效
ValidateRect(&m_client);
//釋放緩衝DC
m_cacheDC.DeleteDC();
//釋放物件
m_cacheCBitmap.DeleteObject();
//釋放視窗DC
ReleaseDC(cDC);
}
//定時器響應函式
void CChildView::OnTimer(UINT_PTR nIDEvent)
{
OnPaint();
}
int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此新增您專用的建立程式碼
//建立一個10毫秒產生一次訊息的定時器
SetTimer(TIMER_PAINT,10,NULL);
return 0;
}
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_scene->UpdateFogArea(point.x,point.y);
}
void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar=='Q' || nChar=='q')
m_scene->ChangeMode();
}
四、遊戲推薦
另外,大家都對遊戲開發感興趣,但是要做出好的遊戲,我們也得首先看看別人都做出了些什麼好玩的東西,感受一下別人的創意。所以從這一節開始,以後霧央會每次給大家推薦一個好玩的小遊戲,一般都是很有創意或很有意思的遊戲,霧央都親測過,當然大眾都知道的就不會推薦了,歡迎關注和向霧央推薦你知道的創意遊戲。
今天給大家推薦一個小遊戲,名字叫“打我啊”,這個遊戲做的比較簡陋,可能是作者隨手弄的,但是挺有意思的。它就是一個打蜜蜂那種的小遊戲,但是每當你勝利進入下一關的時候,AI的操作和你上一關一模一樣,所以不做死就不會死,哈哈。
遊戲地址請戳這裡:
《C++遊戲開發》筆記十四到這裡就結束了,更多精彩請關注下一篇。如果您覺得文章對您有幫助的話,請留下您的評論,點個贊,能看到你們的留言是我最高興的事情,因為這讓我知道我正在幫助曾和我一樣迷茫的少年,你們的支援就是我繼續寫下去的動力,願我們一起學習,共同努力,復興國產遊戲。
對於文章的疏漏或錯誤,歡迎大家的指出。