[心得] 容易被遺忘的遊戲設計模組(4)
"最容易被遺忘的遊戲設計模組"之四:
Update data by first time/changed/every frame
最後一個討論題目。
我們要一再再重申遊戲程式設計的大部分討論都是立基於每個畫格的掙扎之中。
即便是有更好的硬體。運算量也都會因為慾望無窮而被更好的聲光效果而擠壓。
前一個討論中提出的壓縮率-程式碼在畫格中的掙扎-這戰鬥就像柯南一樣永無止盡。
意思是一個畫格中有好幾件事情在搶運算量。搶的等級是在1/30秒甚至1/60之間。
這裡我們不是要討論
"把AI(人工智能)或是讀檔等工作利用奇淫絕技切割在好幾個畫格完成"
這件事情。(延後那些不急的事)
而是-我們要去搾掉那些不該作的事情。
結合前面幾節的討論內容來想像一個情境:
"在過關畫面或是戰鬥勝利的結算畫面有一個分數的欄位
分數不斷的往上跳動,數秒之後到達結果的分數值。"
這段文字的背後運作是像這樣的。
(這裡我們就先不管浮點數精度與整數互換的問題了,那不是我們的重點)
有一個結果分數值的變數 ScoreResult (最終要跳到的數字)
一個目前顯示分數的變數 ScoreNow (還沒跳到的數字)
顯示函式中作的事情
{
...
DrawText( ScoreNow ) ;
...
}
顯示之前我們要依照目前的進度來決定 ScoreNow
因此我們用另一個變數用來設定跳動的速度 :
ScoreJump = 100 (/1sec ) ;// 每秒跳100
當然還要帶入第一節討論的參數 _FPSNow
更新函式中作的事情
{
...
ScoreNow = ScoreNow + ( ScoreJump / _FPSNow ) ;
if( ScoreNow >= ScoreResult )
{
ScoreNow = ScoreResult ;// 避免分數超過之前偷偷算好的總分
// 發出事件說已經算完了 這用到第二節事件處理的概念
}
...
}
我們把更新跟繪圖分開來了,還用到第三節分段的概念。
實務上,通常我們不會定死一個跳動的速度,(先假設我們不用任意鍵skip跳動特效)
因為萬一ScoreResult非常大,或非常小,就會導致跳太久或是跳太快,
兩者都造成我們辛苦設計的跳動特效失效。
因此完成跳動特效有另一個設計 設定要跳多久-ScoreJumpTime
跳動速度的計算就會變成 ScoreResult / ( ScoreJumpTime * _FPSNow )
總共多少要跳 * ( 跳多久 / FPS比例 )
或是變成 ( ScoreResult / ScoreJumpTime ) / _FPSNow
每秒跳多少 換算成每個frame
例如
設定3秒要跳完100,那麼第一個畫格(FPS:30)的時候,應該要跳動
ScoreJumpThisFrame = ( 100.0 / 3.0 ) / 30 = 1.1 ;
應該要完成1 of 90畫格的進度
ScoreNow += ScoreJumpThisFrame ; // 更新目前的分數值
第二畫格(假設是FPS:60)的時候,
ScoreJumpThisFrame = 100.0 / 3.0 / 60 = 0.556 ;
// 100/3事實上可以先算起來放著了 就是更新速率(每秒)
// fps變快了所以每個畫格的改變量反而變少了
接下來想像一個的狀態。我們的顯示數字是整數的,
(但是運算使用浮點數的原因是避免除法有遺漏。)
在某情況下可能每個畫格更新不到1
畫格 : 真實分數 : 顯示分數
0 : 0.0 : 0
1 : 1.1 : 1
2 : 1.656 : 1 // 這個畫格顯示的地方沒更新
再極端一點假定我們最小跳動數值是100,但我們這次遇到的狀況就是在三秒跳完100。
(假設FPS保持在30)
秒 0 1/30 2/30 ... 1.03 1.06 ... ...........2.97 3.0
畫格 0 1 2 31 32 89 90
實際數字 0 1.1 2.2 33.3 34.4 98.9 100
顯示數字 0 0 0 0 0 到這都還是0 100
經過這一連串的情境。最後一行有沒有發現一個事實。
也就是在某種情況之下(其實這情況常常發生),顯示是可以不用動的。
數字從1.1變成2.2對於運算單元是個很小的負擔
但是當我們要把1.1這個數字變成"1.1"這個字串卻是"相對"大的記憶體與計算量。
尤其是當這個字串不動的時候。
更不用說我們可能為了不動的數字拼湊/組合了整個字串像是
"您今天的總分是" + 0 + "分,恭喜恭喜!!"
終於要講到今天的主題也就是資料更新頻率。
除了繪圖之外,其實很多時候資料相對於FPS來講是幾乎靜止的。
以沙漠商旅C為例,當買進一個貨物,或是貨物量有變化的時候
我才有必要因為重量的變化,去更新我整個商旅的負重,來檢查是否超重這件事。
換言之,我不用每個畫格都去統計每樣貨物總重量,
每個成員及運輸工具的負重。
因為事件不會頻繁地在每個畫格中出現。
即便是在室外定期會消耗食物,也只要在消耗的時候去統計。
在以下兩個狀況都滿足之時方才更新資料
1.需要顯示他的時候(看不到不用更新)
2.資料變動的時候(事件)
與FPS相同的,這個設計機制是廣泛到超出我們的想像的。
我們必須要對我們要處理的運算/資料作分類,分出他們需要更新的狀況。
以"叫醫生來看你"來比喻大致上可以分為
Update data by
first time 疫苗-出生後就打一次。
changed 感冒-當感冒的時候才去。
every frame 加護病房-持續觀察,不管他有沒有變化。
因此沙漠商旅C的更新選單這件事會把每個選單(因為每個選單要更新的對象不同)
獨立一個函式,並建立成以下這個架構:
Update(*)Menu()
{
// 每個畫格都會進來
if( true == Changed )
{
// 改變過才要更新的部分
Changed = false ;// 設定旗標讓下次進來的時候略過
}
// 每個畫格都更新的部分
}
因此
在沙漠商旅C的商旅選單中
"商旅名稱" "商旅稱號" 這種字串就是第一類,從檔案讀進來的時候更新一次。
商旅名稱 "小虎隊" 這個字串就是當玩家keyin修改了才去更新選單上的資料。
商旅存在時間 "3天3夜12小時47分鐘" 這個字串可能每分每秒都在跳,
也不適合獨立出一個事件,也許你就可以考慮每個畫格去更新他。
--
"May the Balance be with U"(願平衡與你同在)
視窗介面遊戲設計教學( http://0rz.tw/V28It ),討論,分享。歡迎來信。
視窗程式設計(Windows CLR Form)遊戲架構設計(Game Application Framework)
遊戲工具設計(Game App. Tool Design )
電腦圖學架構及研究(Computer Graphics)論文代讀(含投影片製作)
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.96.77.176
※ 編輯: NDark 來自: 140.96.77.176 (02/11 19:32)
推
02/11 19:42, , 1F
02/11 19:42, 1F
推
02/11 19:49, , 2F
02/11 19:49, 2F
→
02/12 00:13, , 3F
02/12 00:13, 3F
推
02/12 02:44, , 4F
02/12 02:44, 4F
→
02/12 02:44, , 5F
02/12 02:44, 5F
推
02/12 09:36, , 6F
02/12 09:36, 6F
推
02/22 14:45, , 7F
02/22 14:45, 7F
GameDesign 近期熱門文章
PTT遊戲區 即時熱門文章
10
39