夢開始的地方——FC游戲開發(fā)指南(6)精靈詳解
(本系列是一個回歸電子游戲原點的特別系列,作者 @goodorc_gamedev)
之前我們繪制了像素畫,也能讓像素畫顯示到背景中。但繪制背景的性能開銷很大,而且移動也有很多不方便的地方(比如最小單位是8個像素)。
僅僅把圖片顯示到背景中,遠遠不是FC開發(fā)的重點。FC功能之所以如此強大,離不開一個關(guān)鍵的概念——精靈(Sprite)。
1、FC的“精靈”
“精靈”是什么?
一個精靈是一個8x8像素的瓦片(也有8x16的情況,很少用到)。
精靈可以在屏幕中逐像素任意移動位置。
精靈的顯示和移動是PPU的基本功能,不占用可憐的CPU性能,所以極為高效。
每個精靈具有自己獨立的位置、屬性等信息,很像是“面向?qū)ο蟆?。只需要修改精靈的屬性,PPU就會自動改變精靈位置。
有些同學可能會問,如果精靈只有8x8像素,那如果我們的主角比較大怎么辦?典型的馬里奧、西蒙、大金剛身材都不小啊。
答案只能是用多個精靈拼接:

如圖,小馬里奧是4個精靈,大馬里奧是8個精靈。精靈的大小確實限制了FC游戲的性能,所以一般FC游戲的主角不能太大。
2、實踐開始
直擊目標——在上一次代碼的基礎(chǔ)上,直接試驗精靈用法:
代碼重點:
1、定義一個全局精靈對象SPRITE player。每個精靈對象具有x、y坐標,tile編號與特殊屬性attr四個。attr暫時填0即可。
2、在初始化時,填好飛機的位置,tile編號(上次講過,在chr圖表中的編號)。
其中,F(xiàn)C的屏幕大小為256x240像素,所以x、y取值大致就在這個范圍,用u8類型表示。x=100,y=100在屏幕中間區(qū)域。
3、一開始還要加載精靈調(diào)色板,可以暫時沿用背景調(diào)色板。
4、去掉沒用的cprintf,換用load_SP函數(shù)。
load_SP函數(shù)的作用就是將內(nèi)存中的player對象,拷貝到PPU的精靈專用端口。由于我們的飛機位置每一幀都在變化,所以每一幀都要拷貝一次。
這一做法像極了現(xiàn)代PC中 CPU和顯卡的工作方式,CPU每一幀都要將渲染數(shù)據(jù)傳給GPU,叫做一次DrawCall。FC從原理上看,幾乎是一模一樣。
當然,我畫的飛機需要用9個tile拼起來,現(xiàn)在只有一個sprite顯然不夠,只有半個翅膀:

3、多Tile主角
在C語言中,顯然應(yīng)該用數(shù)組來保存多個Sprite數(shù)據(jù)。修改如下:
ok,能自由移動的飛機精靈做好啦~~

4、寫入數(shù)據(jù)到PPU的性能問題
考慮以上代碼的性能會發(fā)現(xiàn),每一次循環(huán)(每一幀)里,消耗性能的地方主要有兩個,一是設(shè)置8個精靈的位置,另一個就是函數(shù)調(diào)用 ?load_SP((u8*)(&player),0,9);
load_SP是如何實現(xiàn)的呢?看源碼:
看懂以上代碼需要對C語言指針有較好的理解。但就算不完全看懂,也可以看出:拷貝n個精靈數(shù)據(jù)到PPU,需要循環(huán)賦值。
循環(huán)時做的事,是讀寫兩個特殊端口:SP_memory_add = 0x2003,SP_memory_dat = 0x2004。通過0x2003和0x2004,就可以將數(shù)據(jù)一個一個傳入到PPU里面。具體端口的定義要參考任天堂的相關(guān)文檔。
但是,注意但是——
FC支持數(shù)十個精靈。如果每一幀同步所有精靈,每個精靈4字節(jié),那么假設(shè)拷貝32個精靈,就需要拷貝 4 * 32 = ?128字節(jié)。而如果通過0x2003和0x2004傳輸數(shù)據(jù),需要128次才能傳完~~~
這種低下的傳輸效率與高速的PPU之間產(chǎn)生了嚴重矛盾。
就好比是用奔騰1代 CPU 驅(qū)動RTX3090顯卡,CPU傳輸太慢,CPU傳1秒才能傳完的數(shù)據(jù),顯卡一瞬間就處理完了。顯卡閑著沒事干。
在FC上,如果CPU一直停留在傳輸狀態(tài),還會造成時序混亂,在屏幕上顯示出奇怪的東西。
預(yù)告:通過DMA高速傳輸精靈數(shù)據(jù)
所以下一期,我們重點解決高速傳輸精靈數(shù)據(jù)的問題——調(diào)用FC的DMA功能,高速傳輸精靈數(shù)據(jù)。
DMA是所有商業(yè)級FC游戲必須使用的技術(shù)方案。敬請期待。
(本文作者?@goodorc_gamedev。歡迎加入游戲開發(fā)群歡樂攪基:1082025059
對游戲開發(fā)感興趣的童鞋可戳這里進一步了解:http://www.levelpp.com/)