日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

从零开始写NES模拟器

發布時間:2023/12/20 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零开始写NES模拟器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

之前寫了如何寫一個nes模擬器,感覺有些語焉不詳,現補充一個小白文章。

FC游戲模擬器是如何工作的

我們小時候很多人玩過任天堂的紅白機游戲。但是它是如何工作的,卻很少有人提及。

今天我們來討論任天堂的游戲機工作機制。首先我們看到的是游戲畫面,實際上老式的電視的分辨率是240*256的。

它是如何工作的呢?

FC游戲機中包含CPU,PPU,卡帶,內存。當我們開機時,CPU加載卡帶的游戲程序到內存中,將游戲中的畫面通過端口寫入到PPU(圖片處理器)中。CPU只需要對PPU進行簡單的端口操作,PPU就能將需要顯示的內容顯示在屏幕上。

?

PPU負責繪圖圖像,請注意PPU繪制了每一個點,即240*256=61440個點。并且繪制這么多點只需要20ms。在1秒的時間內要繪制50幅這樣的圖像。我們的游戲才會如此的流暢。所以要做這個模擬器。我們需要在窗口中繪制連續的圖像。在Windows中我們需要尋找一個能快速繪制圖像的函數。它要在1秒鐘能繪制50幅連續的圖像。

?

第一篇 CreateDIBSection函數的使用

?

在想寫模擬器之前,我們尋找這樣一個函數。我們把圖像的(顏色)RGB值寫入到一個內存中,然后把它畫到窗口中來。

它就是CreateDIBSection。其實有個知名度比較高的函數SetPixel(),但是它太慢了。SetPixel函數是把一個點直接畫到屏幕上來,6萬個點,太耗時了。而CreateDIBSection不一樣,它幫我們分配了一段內存,我們把一屏幕的顏色值都拷貝進去,一次輸出到屏幕,所以速度快。

當然我們還可以用DirectX中的函數,速度更快,先不討論它。

函數的聲明如下:

HBITMAP CreateDIBSection(

HDC hdc, // handle to DC 指向DC的句柄,就是我們窗口的DC,

CONST BITMAPINFO *pbmi, // bitmap data 位圖信息

UINT iUsage, // data type indicator 數據類型,我們需要的是RGB格式

VOID **ppvBits, // bit values 指向內存地址的指針,注意這個函數給我們分配的一段內存,我們只需要提供指針給它就好。

HANDLE hSection, // h 不管它

DWORD dwOffset // offset to bitmap bit values 不管它

);

下面我分別用SetPixel和CreateDIBSection函數實現顯示點陣圖形A。

?

繪制圖形的最基本的是繪制點,我們來實現一下。

/************************************************************************/

/* 注釋: 屏幕畫點函數 - 繪制到內存中

函數名: draw_window_point

參數: x,y 要畫點的坐標,color 顏色值,video 對應內存緩沖地址

width 窗口的寬度 */

/************************************************************************/

void draw_window_point(int x,int y,UINT color,UINT * video,int width)

{

//點的偏移量

int p_offset = 0;

//坐標的值超出了限制,就直接返回

if(y > NES_DISP_WIDTH -1 || x > NES_DISP_WIDTH -1)

return;

//計算點位置,

p_offset = y * width + x;

//畫點,把要繪制的顏色值放入到對應的內存。

*(video + p_offset) = (UINT)color;

//返回1

return;

}

?

由于所有的圖形我們都需要現繪制在內存中,然后一次畫到屏幕上,這樣才能避免速度慢引起的閃爍。

這個函數就是把點繪制到指定的內存中,x,y指明了繪制的坐標,color是顏色值,video是繪制的起始地址,

width是繪制圖形的寬度。現在假如我們要把NES游戲的畫面從PPU中取出并繪制到內存中,那么游戲畫面是

240高*256寬的。假設我們取第3行,第2列的顏色值,放入到 video中,那么p_offset = 3 * 256 + 2。

如果你理解了這些,我們就可以繪制圖形了。在NES的基本塊是被稱為Tile(磚塊),我們先繪制磚塊。

?

第一步,繪制Tile磚塊

Tile在游戲中被定義為8X8像素的,

00010000 一個字節0x10

00101000 一個字節0x28

01000100 一個字節0x44

10000010 一個字節0x82

11111110 一個字節0xfe

10000010 一個字節0x82

10000010 一個字節0x82

10000010 一個字節0x82

例如上圖是以字母A的表示方法,用每一位表示一個點。因為是1位只能表示0或1,上圖只能表示兩種顏色(例如黑和白)。00010000是二進制,而后面的0x10是16進制,二者等價。我們先來繪制它(一個“A”)。

1.使用SetPixel來繪制

首先定義一個數組,就是上面的內容。

//字模數組

unsigned char buffer[]={0x10,0x28,0x44,0x82,0xfe,0x82,0x82,0x82};

然后寫出繪制它的程序:

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++)

{

//從數組中取出1位,如果為1,繪制紅色點。

if(1 == (p[j] >> (7 - i) & 1))

?

SetPixel(hdc,x + i,y + j,RGB(255,0,0));

?

}

}

}

如果沒有意外,你能在坐標(100,100)處看到一個紅色的“A”。在這個函數中怎么從一個字節中取出1位是你需要思考一下的,剩下的都很簡單。

2.使用CreateDIBSection

如你所見,使用 SetPixel是簡單的,由于性能的考量,我們不得考慮更快的實現方式。

這比第一種要復雜的多,你要做好心理準備。

Draw_Char88(hdc,100,100,(unsigned char*)buffer);

hmap = CreateScreen(hWnd);

LoadFrame(g_WorkFrame,hdc,g_pScreenMem,hmap);

這種方式,你需要把圖形寫入到內存中,然后復制到函數為你分配的位圖緩沖中,最后才能顯示到屏幕。我們繼續用上一個函數Draw_Char88,但是把SetPixel替換成了draw_window_point,因為我們不直接輸出到屏幕,而是內存。接下來我們使用 CreateScreen創建了一個位圖段,它為我們分配了一段顯示內存,我們需要把寫入內存的數據拷貝過去。在 LoadFrame中執行了拷貝過程(memcpy),并把圖形繪制到屏幕中。下圖是demo程序的顯示結果。

完成了這2個程序,你應該已經了解點陣圖形的顯示。當然,上面用1位表示顏色,只能有2種顏色肯定是不夠的。如果要表示多種顏色,我們當然期望每個點都能直接用顏色值表示(假如是32位,32*8=256字節,一個Tile圖塊需要256字節表示,顯然占有內存太大了)。但NES內存過小,不可能采用直接顏色值的方式。什么樣的方式能減小使用內存呢?

1、使用多級索引,NES的Tile中的值不是顏色值,而是索引到色盤中;

2,減少顏色數,NES在一共只定義了64種顏色。

NES中使用了4位的顏色索引,在屬性表中存儲了高2位;在圖案表中存儲了低2位,其中前一個圖形塊(8字節)表示顏色的bit0位,后一個圖形塊(8字節)表示顏色的bit1位。

下圖顯示了低2位是如何表示的,注意圈中的數字,Address中-$000E 的最高為1,$0006中最高位是1,那么圖形的第7行第1個顏色的索引就是3(Result中圈中數字).

我們就來利用低2位繪制圖案表。

總結

以上是生活随笔為你收集整理的从零开始写NES模拟器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。