1. 程式人生 > >《C++遊戲開發》筆記十四 平滑過渡的戰爭迷霧(二) 實現:真正的迷霧來了

《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++遊戲開發》筆記十四到這裡就結束了,更多精彩請關注下一篇。如果您覺得文章對您有幫助的話,請留下您的評論,點個贊,能看到你們的留言是我最高興的事情,因為這讓我知道我正在幫助曾和我一樣迷茫的少年,你們的支援就是我繼續寫下去的動力,願我們一起學習,共同努力,復興國產遊戲。

      對於文章的疏漏或錯誤,歡迎大家的指出。