明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

游戲中的多任務(wù)處理

[摘要]---- Windows 最 杰 出 的 功 能 之 一 是 能 夠 同 時(shí) 運(yùn) 行 多 個(gè) 程 序, 但 有 時(shí) 也 會 讓 人 感 到 頭 疼, 特 別 是 對 于 那 些 習(xí) 慣 于 完 全 控 制 計(jì) 算 機(jī) 甚 至 時(shí) 鐘 頻 率、 非 常 自 信 的 游 戲 程 序 員( 當(dāng) 然, 我...
---- Windows 最 杰 出 的 功 能 之 一 是 能 夠 同 時(shí) 運(yùn) 行 多 個(gè) 程 序, 但 有 時(shí) 也 會 讓 人 感 到 頭 疼, 特 別 是 對 于 那 些 習(xí) 慣 于 完 全 控 制 計(jì) 算 機(jī) 甚 至 時(shí) 鐘 頻 率、 非 常 自 信 的 游 戲 程 序 員( 當(dāng) 然, 我 們 的 確 在 乎 那 些 沒 禮 貌 的、 在 退 出 時(shí) 不 恢 復(fù) 正 確 的 系 統(tǒng) 時(shí) 間 的 游 戲。 但 是 幸 好, 現(xiàn) 在 我 們 可 以 忘 掉 這 些 了)。  
---- 在 多 任 務(wù) 環(huán) 境 下, 游 戲 程 序 員 需 要 注 意 三 個(gè) 大 的 負(fù) 效 應(yīng):  
當(dāng) 游 戲 失 去 焦 點(diǎn) 而 進(jìn) 入 后 臺 后, 其 執(zhí) 行 不 得 不 被 掛 起( 可 以 在 Moby Dick Windows 中 使 用“ 中 止 的” 變 量 觀 察 它 是 如 何 工 作 的)。 如 果 是 一 個(gè) 實(shí) 時(shí) 游 戲, 程 序 員 當(dāng) 然 希 望 它 被 懸 掛。 但 在 回 合 制 游 戲 中, 當(dāng) 玩 家 去 做 其 它 事 情 時(shí), 程 序 員 可 能 不 希 望 計(jì) 算 機(jī) 一 方 作 任 何 動 作, 但 希 望 后 臺 的 人 工 智 能(AI) 運(yùn) 算 依 舊 執(zhí) 行。  
其 它 的 任 務(wù) 占 用 CPU 時(shí) 間, 結(jié) 果 造 成 我 們 不 能 一 直 控 制 游 戲 中 事 情 發(fā) 生 時(shí) 的 速 度。 我 們 將 在 后 面 討 論 這 個(gè) 痛 苦 的 問 題。  
每 當(dāng) 游 戲 回 到 前 臺, 程 序 員 不 得 不 重 畫 游 戲 窗 口。Windows 并 不 負(fù) 責(zé) 記 憶 它 所 覆 蓋 或 隱 藏 的 窗 口 的 內(nèi) 容; 它 所 能 做 的 最 多 是 通 知 一 個(gè) 窗 口 需 要 重 畫 其 客 戶 區(qū) 域。 這 在 有 關(guān) Windows 的 文 章 中 都 有 論 述( 參 見 WM_PAINT 的 內(nèi) 容), 我 們 在 這 里 就 不 討 論 了。 事 實(shí) 上,Moby Dick Windows 并 不 恢 復(fù) 其 自 己 的 窗 口; 我 們 將 在 講 到 DirectDraw 下 的 雙 緩 沖 時(shí) 看 它 是 如 何 實(shí) 現(xiàn) 的。  
程 序 中 的 多 任 務(wù)  
---- 盡 管 Moby Dick DOS 在 使 用 中 斷 處 理 程 序 時(shí) 展 示 了 內(nèi) 部 多 任 務(wù)( 或 者 說 多 線 程) 的 一 種 原 始 形 式, 但 是 該 程 序 仍 然 沒 有 突 破 DOS 的 單 主 題 特 性, 即 在 一 個(gè) 時(shí) 間 只 做 一 件 事 情。 有 些 DOS 程 序 的 確 作 到 了 真 正 的 多 線 程, 但 是 那 需 要 非 常 巨 大 的 編 程 工 作。Windows 95 SDK 使 這 項(xiàng) 工 作 簡 單 了 許 多, 把 線 程 放 進(jìn) 每 一 個(gè) 游 戲 開 發(fā) 者 的“ 錦 囊” 之 中( 如 果 讀 者 還 不 熟 悉 這 個(gè) 概 念, 那 么 簡 單 說 明 一 下, 一 個(gè) 線 程 就 是 程 序 的 一 部 分, 它 執(zhí) 行 時(shí) 獨(dú) 立 于 其 它 的 部 分, 并 且 不 需 要 與 其 它 部 分 同 步。 線 程 不 是 由 中 斷 來 驅(qū) 動 的; 它 們 只 是 在 每 一 次 Windows 給 它 們 CPU 時(shí) 間 時(shí) 繼 續(xù) 其 執(zhí) 行。)  

---- 在 下 列 情 況 下, 可 能 要 考 慮 實(shí) 現(xiàn) 獨(dú) 立 的 線 程:  

允 許 后 臺 AI。 就 算 是 用 戶 正 忙 于 來 回 移 動(moving pieces around)、 打 開 對 話 框 等 事 情, 計(jì) 算 機(jī) 也 能 夠 考 慮 其 下 一 步。 處 理 這 類 線 程 非 常 方 便, 因 為 它 不 需 要 與 其 它 的 事 情 同 步。  

預(yù) 先 加 載 數(shù) 據(jù)。 例 如, 當(dāng) 玩 家 正 努 力 向 下 一 級 奮 斗 時(shí), 使 一 個(gè) 線 程 負(fù) 責(zé) 讀 取 文 件 并 準(zhǔn) 備 好 游 戲“ 世 界”。  

給 予 時(shí) 間 緊 迫(time-critical) 的 任 務(wù) 優(yōu) 先 權(quán)。 我 們 將 在 后 面 會 到 這 個(gè) 主 題。  
游 戲 循 環(huán)  
---- 游 戲 循 環(huán) 的 概 念 在 各 編 程 環(huán) 境 下 都 比 較 相 似。 第 一 步, 需 要 獲 取 輸 入: 可 以 是 輪 詢 它, 等 待 它, 或 者 在 它“ 運(yùn) 行” 時(shí) 通 過 中 斷 或 一 個(gè) 消 息 隊(duì) 列 攔 截 它。 第 二 步, 處 理 該 輸 入, 并 且 把 它 變 成 一 個(gè) 在 游 戲 中 有 實(shí) 際 意 義 的 動 作, 如: 使 飛 機(jī) 傾 斜 飛 行 或 小 卒 向 前 走 一 步。 然 后, 把 結(jié) 果 顯 示 出 來。 當(dāng) 然, 在 這 個(gè) 主 題 中 也 要 求 精 雕 細(xì) 刻 和“ 變 奏 曲”, 包 括 計(jì) 算 AI 的 移 動、 把 控 制 權(quán) 從 一 個(gè) 玩 家 移 交 給 另 一 個(gè) 玩 家、 檢 查 勝 負(fù) 等 等。  

---- 然 而, 在 Windows 和 DOS 下 實(shí) 現(xiàn) 循 環(huán) 的 機(jī) 制 迥 然 不 同。 每 個(gè) Windows 程 序 都 建 立 于 一 個(gè) 消 息 循 環(huán) 之 上。 盡 管 一 個(gè) 游 戲 循 環(huán) 可 以 建 立 在 消 息 循 環(huán) 之 上, 但 是 這 兩 者 仍 有 本 質(zhì) 的 差 別。  

Moby Dick DOS 的 循 環(huán)  
---- Moby Dick DOS 演 示 了 一 種 簡 單 的 游 戲 循 環(huán), 在 這 里 我 們 所 做 的 工 作 是: (a) 檢 查 是 否 有 什 么 東 西 要 移 動, (b) 移 動 它, (c) 顯 示 結(jié) 果。  


while (!gamedone)

{

  //調(diào)用時(shí)間程序 -如果時(shí)間未到,則沒有任何響應(yīng)。

  AhabMoved = Move_Ahab();



  //僅當(dāng) Ahab沒有移動時(shí)移動 Moby Dick。

  //否則它們可能擦肩而過卻不能攔截。

  if (!AhabMoved) Move_Moby();



  //如果有任何一個(gè)移動,更新屏幕,

  //并檢查是否有勝利和失敗。

  if ((MobyX != OldMobyX) (MobyY != OldMobyY)

     (AhabMoved))

  {

   UpdateScreen();



   if ((MobyX == AhabX) && (MobyY == AhabY)

      && (painted[MobyX][MobyY]))

   {

    gamedone = 1;

    cprintf("\a");

    cprintf("You win!");

   }

   if (TimesUp <= 0) { cprintf("\a"); cprintf("Time's up!"); gamedone="1;" } if (raw_key="=" MAKE_ESC) { gamedone="1;" progdone="1;" } } //結(jié)束更新 } //結(jié)束游戲內(nèi)部循環(huán) (while !gamedone) Moby Dick Windows的循環(huán) 從表面看來,好像沒有多大的差別: do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message="=" WM_QUIT) break; //唯一的退出循環(huán)的出口。 TranslateMessage(&msg); DispatchMessage(&msg); } else { if ((MobyX !="OldMobyX)" (MobyY !="OldMobyY)" (AhabMoved)) { UpdateScreen(); if ((AhabX="=" MobyX) && (AhabY="=" MobyY) && (painted[AhabY][AhabX])) { Control="MessageBoxEx(hwnd," "You caught Moby! Play again?", "Call Me Ishmael", MB_ICONQUESTION MB_YESNO, 0); if (Control="=" IDYES) InitializeGame(); else break; } } //如果有人移動了 } //如果屏幕已更新 } //結(jié)束循環(huán) while (TRUE);
---- 前 面 已 經(jīng) 提 到 過, 這 里 沒 有 檢 查 是 否 運(yùn) 行 超 時(shí), 請 忽 略 它, 筆 者 在 Windows 版 中 未 實(shí) 現(xiàn) 它 是 為 了 避 免 令 人 煩 惱 的 中 斷。 中 斷 并 退 出 無 限 循 環(huán) 的 機(jī) 制 有 一 點(diǎn) 兒 而 且 并 不 重 要。 我 們 把 精 力 集 中 在 消 息 循 環(huán) 本 身, 所 以 把 其 它 的 無 關(guān) 代 碼 都 刪 掉, 只 留 下 最 基 本 的 部 分:


do

{

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

{

if (msg.message == WM_QUIT) break;

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else DoSomething();

}

while (TRUE)


---- 這 是 一 個(gè) 非 常 典 型 的 消 息 循 環(huán)。 唯 一 有 點(diǎn) 特 殊 的 地 方 就 是 它 使 用 的 是 PeekMessage 而 不 是 GetMessage。

GetMessage 與 PeekMessage 的 比 較
---- 為 什 么 要 用 PeekMessage 呢 ? 原 因 很 簡 單,GetMessage 等 待 一 個(gè) 消 息( 就 像 _getch), 而 PeekMessage 不 是 這 樣( 就 像_kbhit)。 請 考 慮 下 面 的 循 環(huán):


while (GetMessage(&msg, NULL, 0, 0))

{

// 我 們 并 不 進(jìn) 入 括 號 內(nèi) 部, 直 到 有 一 個(gè) 消 息

TranslateMessage(&msg);

DispatchMessage(&msg);

DoSomething()

}

// 當(dāng) GetMessage 返 回 NULL 時(shí), 退 出 該 程 序

return msg.wParam;


---- 在 這 里,DoSomething 不 會 完 成, 除 非 一 個(gè) 消 息-- 或 許 多 消 息-- 被 放 入 隊(duì) 列 中 并 被 處 理。 如 果 DoSomething 恰 好 產(chǎn) 生 一 個(gè) 消 息, 例 如, 如 果 它 更 新 了 屏 幕 并 且 因 此 而 產(chǎn) 生 了 一 個(gè) WM_PAINT 消 息, 那 么 好 了, 水 泵 注 水 后 將 開 始 啟 動 了。 要 使 DoSomething 可 靠 地 完 成 其 工 作, 這 并 不 是 一 個(gè) 好 方 法, 它 使 代 碼 有 點(diǎn) 混 淆, 但 它 工 作 的 還 不 錯。

---- 相 比 之 下,PeekMessage 則 無 論 是 否 有 消 息 在 等 待, 只 要 檢 查 一 下 消 息 隊(duì) 列 就 完 成 其 操 作(yields the floor)。 在 我 們 的 例 子 中, 我 們 實(shí) 際 上 是 使 用 PeekMessage 來 處 理 消 息 的( 通 過 分 發(fā) 它 所 找 到 的 每 一 個(gè) 消 息 并 使 用 PM_REMOVE 參 數(shù) 從 隊(duì) 列 中 清 除 它)。 同 下 面 同 樣 有 效 的 代 碼 相 比, 它 要 更 加 直 接:


if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))

{

if (!GetMessage(&msg, NULL, 0, 0)) break;

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else DoSomething();


---- 在 這 里 有 非 常 重 要 的 一 點(diǎn) 要 說 明, 我 們 的 偽 代 碼 DoSomething 是 獨(dú) 立 于 消 息 的; 無 論 隊(duì) 列 中 送 出 的 什 么 消 息, 甚 至 無 論 有 沒 有 消 息 在 那 兒, 它 都 將 執(zhí) 行。 在 Moby Dick 中, 我 們 將 屏 幕 更 新 和 勝 利 條 件 的 檢 查 放 在 這 里, 因 為 在 這 里 檢 查 一 個(gè) 或 多 個(gè) 消 息 被 響 應(yīng) 后 是 否 需 要 更 新 屏 幕 或 是 否 達(dá) 到 勝 利 條 件 很 方 便。

---- 那 么, 消 息 循 環(huán) 就 是 游 戲 循 環(huán) 嗎 ? 從 抽 象 的 角 度, 是 的, 因 為 它 是 大 的 齒 輪, 帶 動 那 些 小 的 齒 輪。 但 是, 盡 管 把 一 些 函 數(shù) 調(diào) 用 放 在 此 處 可 能 比 較 方 便,Windows 編 程 規(guī) 則 卻 要 求 任 何 響 應(yīng) 一 個(gè) 消 息 的 動 作 都 應(yīng) 該 放 在 消 息 響 應(yīng) 程 序 中( 就 是 說, 放 在 窗 口 過 程 中)。 在 一 個(gè) 實(shí) 時(shí) 游 戲 中, 絕 大 多 數(shù) 的 動 作 發(fā) 生 在 一 個(gè) 或 多 個(gè) WM_TIMER 消 息 響 應(yīng) 程 序 中。 回 合 制 游 戲 則 常 常 在 輸 入 消 息 的 響 應(yīng) 函 數(shù) 中 做 大 量 的 工 作。 


相關(guān)文章