从零开始写NES模拟器
?
之前寫(xiě)了如何寫(xiě)一個(gè)nes模擬器,感覺(jué)有些語(yǔ)焉不詳,現(xiàn)補(bǔ)充一個(gè)小白文章。
FC游戲模擬器是如何工作的
我們小時(shí)候很多人玩過(guò)任天堂的紅白機(jī)游戲。但是它是如何工作的,卻很少有人提及。
今天我們來(lái)討論任天堂的游戲機(jī)工作機(jī)制。首先我們看到的是游戲畫(huà)面,實(shí)際上老式的電視的分辨率是240*256的。
它是如何工作的呢?
FC游戲機(jī)中包含CPU,PPU,卡帶,內(nèi)存。當(dāng)我們開(kāi)機(jī)時(shí),CPU加載卡帶的游戲程序到內(nèi)存中,將游戲中的畫(huà)面通過(guò)端口寫(xiě)入到PPU(圖片處理器)中。CPU只需要對(duì)PPU進(jìn)行簡(jiǎn)單的端口操作,PPU就能將需要顯示的內(nèi)容顯示在屏幕上。
?
PPU負(fù)責(zé)繪圖圖像,請(qǐng)注意PPU繪制了每一個(gè)點(diǎn),即240*256=61440個(gè)點(diǎn)。并且繪制這么多點(diǎn)只需要20ms。在1秒的時(shí)間內(nèi)要繪制50幅這樣的圖像。我們的游戲才會(huì)如此的流暢。所以要做這個(gè)模擬器。我們需要在窗口中繪制連續(xù)的圖像。在Windows中我們需要尋找一個(gè)能快速繪制圖像的函數(shù)。它要在1秒鐘能繪制50幅連續(xù)的圖像。
?
第一篇 CreateDIBSection函數(shù)的使用
?
在想寫(xiě)模擬器之前,我們尋找這樣一個(gè)函數(shù)。我們把圖像的(顏色)RGB值寫(xiě)入到一個(gè)內(nèi)存中,然后把它畫(huà)到窗口中來(lái)。
它就是CreateDIBSection。其實(shí)有個(gè)知名度比較高的函數(shù)SetPixel(),但是它太慢了。SetPixel函數(shù)是把一個(gè)點(diǎn)直接畫(huà)到屏幕上來(lái),6萬(wàn)個(gè)點(diǎn),太耗時(shí)了。而CreateDIBSection不一樣,它幫我們分配了一段內(nèi)存,我們把一屏幕的顏色值都拷貝進(jìn)去,一次輸出到屏幕,所以速度快。
當(dāng)然我們還可以用DirectX中的函數(shù),速度更快,先不討論它。
函數(shù)的聲明如下:
HBITMAP CreateDIBSection(
HDC hdc, // handle to DC 指向DC的句柄,就是我們窗口的DC,
CONST BITMAPINFO *pbmi, // bitmap data 位圖信息
UINT iUsage, // data type indicator 數(shù)據(jù)類(lèi)型,我們需要的是RGB格式
VOID **ppvBits, // bit values 指向內(nèi)存地址的指針,注意這個(gè)函數(shù)給我們分配的一段內(nèi)存,我們只需要提供指針給它就好。
HANDLE hSection, // h 不管它
DWORD dwOffset // offset to bitmap bit values 不管它
);
下面我分別用SetPixel和CreateDIBSection函數(shù)實(shí)現(xiàn)顯示點(diǎn)陣圖形A。
?
繪制圖形的最基本的是繪制點(diǎn),我們來(lái)實(shí)現(xiàn)一下。
/************************************************************************/
/* 注釋: 屏幕畫(huà)點(diǎn)函數(shù) - 繪制到內(nèi)存中
函數(shù)名: draw_window_point
參數(shù): x,y 要畫(huà)點(diǎn)的坐標(biāo),color 顏色值,video 對(duì)應(yīng)內(nèi)存緩沖地址
width 窗口的寬度 */
/************************************************************************/
void draw_window_point(int x,int y,UINT color,UINT * video,int width)
{
//點(diǎn)的偏移量
int p_offset = 0;
//坐標(biāo)的值超出了限制,就直接返回
if(y > NES_DISP_WIDTH -1 || x > NES_DISP_WIDTH -1)
return;
//計(jì)算點(diǎn)位置,
p_offset = y * width + x;
//畫(huà)點(diǎn),把要繪制的顏色值放入到對(duì)應(yīng)的內(nèi)存。
*(video + p_offset) = (UINT)color;
//返回1
return;
}
?
由于所有的圖形我們都需要現(xiàn)繪制在內(nèi)存中,然后一次畫(huà)到屏幕上,這樣才能避免速度慢引起的閃爍。
這個(gè)函數(shù)就是把點(diǎn)繪制到指定的內(nèi)存中,x,y指明了繪制的坐標(biāo),color是顏色值,video是繪制的起始地址,
width是繪制圖形的寬度。現(xiàn)在假如我們要把NES游戲的畫(huà)面從PPU中取出并繪制到內(nèi)存中,那么游戲畫(huà)面是
240高*256寬的。假設(shè)我們?nèi)〉?行,第2列的顏色值,放入到 video中,那么p_offset = 3 * 256 + 2。
如果你理解了這些,我們就可以繪制圖形了。在NES的基本塊是被稱(chēng)為T(mén)ile(磚塊),我們先繪制磚塊。
?
第一步,繪制Tile磚塊
Tile在游戲中被定義為8X8像素的,
00010000 一個(gè)字節(jié)0x10
00101000 一個(gè)字節(jié)0x28
01000100 一個(gè)字節(jié)0x44
10000010 一個(gè)字節(jié)0x82
11111110 一個(gè)字節(jié)0xfe
10000010 一個(gè)字節(jié)0x82
10000010 一個(gè)字節(jié)0x82
10000010 一個(gè)字節(jié)0x82
例如上圖是以字母A的表示方法,用每一位表示一個(gè)點(diǎn)。因?yàn)槭?位只能表示0或1,上圖只能表示兩種顏色(例如黑和白)。00010000是二進(jìn)制,而后面的0x10是16進(jìn)制,二者等價(jià)。我們先來(lái)繪制它(一個(gè)“A”)。
1.使用SetPixel來(lái)繪制
首先定義一個(gè)數(shù)組,就是上面的內(nèi)容。
//字模數(shù)組
unsigned char buffer[]={0x10,0x28,0x44,0x82,0xfe,0x82,0x82,0x82};
然后寫(xiě)出繪制它的程序:
void Draw_Char88(HDC hdc,int x,int y,unsigned char * p)
{
int i,j;
?
//繪制圖案
for (j =0;j < 8;j++)
{
for (i = 0;i < 8; i++)
{
//從數(shù)組中取出1位,如果為1,繪制紅色點(diǎn)。
if(1 == (p[j] >> (7 - i) & 1))
?
SetPixel(hdc,x + i,y + j,RGB(255,0,0));
?
}
}
}
如果沒(méi)有意外,你能在坐標(biāo)(100,100)處看到一個(gè)紅色的“A”。在這個(gè)函數(shù)中怎么從一個(gè)字節(jié)中取出1位是你需要思考一下的,剩下的都很簡(jiǎn)單。
2.使用CreateDIBSection
如你所見(jiàn),使用 SetPixel是簡(jiǎn)單的,由于性能的考量,我們不得考慮更快的實(shí)現(xiàn)方式。
這比第一種要復(fù)雜的多,你要做好心理準(zhǔn)備。
Draw_Char88(hdc,100,100,(unsigned char*)buffer);
hmap = CreateScreen(hWnd);
LoadFrame(g_WorkFrame,hdc,g_pScreenMem,hmap);
這種方式,你需要把圖形寫(xiě)入到內(nèi)存中,然后復(fù)制到函數(shù)為你分配的位圖緩沖中,最后才能顯示到屏幕。我們繼續(xù)用上一個(gè)函數(shù)Draw_Char88,但是把SetPixel替換成了draw_window_point,因?yàn)槲覀儾恢苯虞敵龅狡聊?#xff0c;而是內(nèi)存。接下來(lái)我們使用 CreateScreen創(chuàng)建了一個(gè)位圖段,它為我們分配了一段顯示內(nèi)存,我們需要把寫(xiě)入內(nèi)存的數(shù)據(jù)拷貝過(guò)去。在 LoadFrame中執(zhí)行了拷貝過(guò)程(memcpy),并把圖形繪制到屏幕中。下圖是demo程序的顯示結(jié)果。
完成了這2個(gè)程序,你應(yīng)該已經(jīng)了解點(diǎn)陣圖形的顯示。當(dāng)然,上面用1位表示顏色,只能有2種顏色肯定是不夠的。如果要表示多種顏色,我們當(dāng)然期望每個(gè)點(diǎn)都能直接用顏色值表示(假如是32位,32*8=256字節(jié),一個(gè)Tile圖塊需要256字節(jié)表示,顯然占有內(nèi)存太大了)。但NES內(nèi)存過(guò)小,不可能采用直接顏色值的方式。什么樣的方式能減小使用內(nèi)存呢?
1、使用多級(jí)索引,NES的Tile中的值不是顏色值,而是索引到色盤(pán)中;
2,減少顏色數(shù),NES在一共只定義了64種顏色。
NES中使用了4位的顏色索引,在屬性表中存儲(chǔ)了高2位;在圖案表中存儲(chǔ)了低2位,其中前一個(gè)圖形塊(8字節(jié))表示顏色的bit0位,后一個(gè)圖形塊(8字節(jié))表示顏色的bit1位。
下圖顯示了低2位是如何表示的,注意圈中的數(shù)字,Address中-$000E 的最高為1,$0006中最高位是1,那么圖形的第7行第1個(gè)顏色的索引就是3(Result中圈中數(shù)字).
我們就來(lái)利用低2位繪制圖案表。
總結(jié)
以上是生活随笔為你收集整理的从零开始写NES模拟器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 《即刻电音》蒋大为坤音四子助阵“电音春晚
- 下一篇: mac硬盘空间不足