明辉站/网站教程/内容

游戏中的多任务处理

网站教程2024-02-14 阅读
[摘要]---- Windows 最 杰 出 的 功 能 之 一 是 能 够 同 时 运 行 多 个 程 序, 但 有 时 也 会 让 人 感 到 头 疼, 特 别 是 对 于 那 些 习 惯 于 完 全 控 制 计 算 机 甚 至 时 钟 频 率、 非 常 自 信 的 游 戏 程 序 员( 当 然, 我...
---- Windows 最 杰 出 的 功 能 之 一 是 能 够 同 时 运 行 多 个 程 序, 但 有 时 也 会 让 人 感 到 头 疼, 特 别 是 对 于 那 些 习 惯 于 完 全 控 制 计 算 机 甚 至 时 钟 频 率、 非 常 自 信 的 游 戏 程 序 员( 当 然, 我 们 的 确 在 乎 那 些 没 礼 貌 的、 在 退 出 时 不 恢 复 正 确 的 系 统 时 间 的 游 戏。 但 是 幸 好, 现 在 我 们 可 以 忘 掉 这 些 了)。  
---- 在 多 任 务 环 境 下, 游 戏 程 序 员 需 要 注 意 三 个 大 的 负 效 应:  
当 游 戏 失 去 焦 点 而 进 入 后 台 后, 其 执 行 不 得 不 被 挂 起( 可 以 在 Moby Dick Windows 中 使 用“ 中 止 的” 变 量 观 察 它 是 如 何 工 作 的)。 如 果 是 一 个 实 时 游 戏, 程 序 员 当 然 希 望 它 被 悬 挂。 但 在 回 合 制 游 戏 中, 当 玩 家 去 做 其 它 事 情 时, 程 序 员 可 能 不 希 望 计 算 机 一 方 作 任 何 动 作, 但 希 望 后 台 的 人 工 智 能(AI) 运 算 依 旧 执 行。  
其 它 的 任 务 占 用 CPU 时 间, 结 果 造 成 我 们 不 能 一 直 控 制 游 戏 中 事 情 发 生 时 的 速 度。 我 们 将 在 后 面 讨 论 这 个 痛 苦 的 问 题。  
每 当 游 戏 回 到 前 台, 程 序 员 不 得 不 重 画 游 戏 窗 口。Windows 并 不 负 责 记 忆 它 所 覆 盖 或 隐 藏 的 窗 口 的 内 容; 它 所 能 做 的 最 多 是 通 知 一 个 窗 口 需 要 重 画 其 客 户 区 域。 这 在 有 关 Windows 的 文 章 中 都 有 论 述( 参 见 WM_PAINT 的 内 容), 我 们 在 这 里 就 不 讨 论 了。 事 实 上,Moby Dick Windows 并 不 恢 复 其 自 己 的 窗 口; 我 们 将 在 讲 到 DirectDraw 下 的 双 缓 冲 时 看 它 是 如 何 实 现 的。  
程 序 中 的 多 任 务  
---- 尽 管 Moby Dick DOS 在 使 用 中 断 处 理 程 序 时 展 示 了 内 部 多 任 务( 或 者 说 多 线 程) 的 一 种 原 始 形 式, 但 是 该 程 序 仍 然 没 有 突 破 DOS 的 单 主 题 特 性, 即 在 一 个 时 间 只 做 一 件 事 情。 有 些 DOS 程 序 的 确 作 到 了 真 正 的 多 线 程, 但 是 那 需 要 非 常 巨 大 的 编 程 工 作。Windows 95 SDK 使 这 项 工 作 简 单 了 许 多, 把 线 程 放 进 每 一 个 游 戏 开 发 者 的“ 锦 囊” 之 中( 如 果 读 者 还 不 熟 悉 这 个 概 念, 那 么 简 单 说 明 一 下, 一 个 线 程 就 是 程 序 的 一 部 分, 它 执 行 时 独 立 于 其 它 的 部 分, 并 且 不 需 要 与 其 它 部 分 同 步。 线 程 不 是 由 中 断 来 驱 动 的; 它 们 只 是 在 每 一 次 Windows 给 它 们 CPU 时 间 时 继 续 其 执 行。)  

---- 在 下 列 情 况 下, 可 能 要 考 虑 实 现 独 立 的 线 程:  

允 许 后 台 AI。 就 算 是 用 户 正 忙 于 来 回 移 动(moving pieces around)、 打 开 对 话 框 等 事 情, 计 算 机 也 能 够 考 虑 其 下 一 步。 处 理 这 类 线 程 非 常 方 便, 因 为 它 不 需 要 与 其 它 的 事 情 同 步。  

预 先 加 载 数 据。 例 如, 当 玩 家 正 努 力 向 下 一 级 奋 斗 时, 使 一 个 线 程 负 责 读 取 文 件 并 准 备 好 游 戏“ 世 界”。  

给 予 时 间 紧 迫(time-critical) 的 任 务 优 先 权。 我 们 将 在 后 面 会 到 这 个 主 题。  
游 戏 循 环  
---- 游 戏 循 环 的 概 念 在 各 编 程 环 境 下 都 比 较 相 似。 第 一 步, 需 要 获 取 输 入: 可 以 是 轮 询 它, 等 待 它, 或 者 在 它“ 运 行” 时 通 过 中 断 或 一 个 消 息 队 列 拦 截 它。 第 二 步, 处 理 该 输 入, 并 且 把 它 变 成 一 个 在 游 戏 中 有 实 际 意 义 的 动 作, 如: 使 飞 机 倾 斜 飞 行 或 小 卒 向 前 走 一 步。 然 后, 把 结 果 显 示 出 来。 当 然, 在 这 个 主 题 中 也 要 求 精 雕 细 刻 和“ 变 奏 曲”, 包 括 计 算 AI 的 移 动、 把 控 制 权 从 一 个 玩 家 移 交 给 另 一 个 玩 家、 检 查 胜 负 等 等。  

---- 然 而, 在 Windows 和 DOS 下 实 现 循 环 的 机 制 迥 然 不 同。 每 个 Windows 程 序 都 建 立 于 一 个 消 息 循 环 之 上。 尽 管 一 个 游 戏 循 环 可 以 建 立 在 消 息 循 环 之 上, 但 是 这 两 者 仍 有 本 质 的 差 别。  

Moby Dick DOS 的 循 环  
---- Moby Dick DOS 演 示 了 一 种 简 单 的 游 戏 循 环, 在 这 里 我 们 所 做 的 工 作 是: (a) 检 查 是 否 有 什 么 东 西 要 移 动, (b) 移 动 它, (c) 显 示 结 果。  


while (!gamedone)

{

  //调用时间程序 -如果时间未到,则没有任何响应。

  AhabMoved = Move_Ahab();



  //仅当 Ahab没有移动时移动 Moby Dick。

  //否则它们可能擦肩而过却不能拦截。

  if (!AhabMoved) Move_Moby();



  //如果有任何一个移动,更新屏幕,

  //并检查是否有胜利和失败。

  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;" } } //结束更新 } //结束游戏内部循环 (while !gamedone) Moby Dick Windows的循环 从表面看来,好像没有多大的差别: do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message="=" WM_QUIT) break; //唯一的退出循环的出口。 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; } } //如果有人移动了 } //如果屏幕已更新 } //结束循环 while (TRUE);
---- 前 面 已 经 提 到 过, 这 里 没 有 检 查 是 否 运 行 超 时, 请 忽 略 它, 笔 者 在 Windows 版 中 未 实 现 它 是 为 了 避 免 令 人 烦 恼 的 中 断。 中 断 并 退 出 无 限 循 环 的 机 制 有 一 点 儿 而 且 并 不 重 要。 我 们 把 精 力 集 中 在 消 息 循 环 本 身, 所 以 把 其 它 的 无 关 代 码 都 删 掉, 只 留 下 最 基 本 的 部 分:


do

{

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

{

if (msg.message == WM_QUIT) break;

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else DoSomething();

}

while (TRUE)


---- 这 是 一 个 非 常 典 型 的 消 息 循 环。 唯 一 有 点 特 殊 的 地 方 就 是 它 使 用 的 是 PeekMessage 而 不 是 GetMessage。

GetMessage 与 PeekMessage 的 比 较
---- 为 什 么 要 用 PeekMessage 呢 ? 原 因 很 简 单,GetMessage 等 待 一 个 消 息( 就 像 _getch), 而 PeekMessage 不 是 这 样( 就 像_kbhit)。 请 考 虑 下 面 的 循 环:


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

{

// 我 们 并 不 进 入 括 号 内 部, 直 到 有 一 个 消 息

TranslateMessage(&msg);

DispatchMessage(&msg);

DoSomething()

}

// 当 GetMessage 返 回 NULL 时, 退 出 该 程 序

return msg.wParam;


---- 在 这 里,DoSomething 不 会 完 成, 除 非 一 个 消 息-- 或 许 多 消 息-- 被 放 入 队 列 中 并 被 处 理。 如 果 DoSomething 恰 好 产 生 一 个 消 息, 例 如, 如 果 它 更 新 了 屏 幕 并 且 因 此 而 产 生 了 一 个 WM_PAINT 消 息, 那 么 好 了, 水 泵 注 水 后 将 开 始 启 动 了。 要 使 DoSomething 可 靠 地 完 成 其 工 作, 这 并 不 是 一 个 好 方 法, 它 使 代 码 有 点 混 淆, 但 它 工 作 的 还 不 错。

---- 相 比 之 下,PeekMessage 则 无 论 是 否 有 消 息 在 等 待, 只 要 检 查 一 下 消 息 队 列 就 完 成 其 操 作(yields the floor)。 在 我 们 的 例 子 中, 我 们 实 际 上 是 使 用 PeekMessage 来 处 理 消 息 的( 通 过 分 发 它 所 找 到 的 每 一 个 消 息 并 使 用 PM_REMOVE 参 数 从 队 列 中 清 除 它)。 同 下 面 同 样 有 效 的 代 码 相 比, 它 要 更 加 直 接:


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

{

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

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else DoSomething();


---- 在 这 里 有 非 常 重 要 的 一 点 要 说 明, 我 们 的 伪 代 码 DoSomething 是 独 立 于 消 息 的; 无 论 队 列 中 送 出 的 什 么 消 息, 甚 至 无 论 有 没 有 消 息 在 那 儿, 它 都 将 执 行。 在 Moby Dick 中, 我 们 将 屏 幕 更 新 和 胜 利 条 件 的 检 查 放 在 这 里, 因 为 在 这 里 检 查 一 个 或 多 个 消 息 被 响 应 后 是 否 需 要 更 新 屏 幕 或 是 否 达 到 胜 利 条 件 很 方 便。

---- 那 么, 消 息 循 环 就 是 游 戏 循 环 吗 ? 从 抽 象 的 角 度, 是 的, 因 为 它 是 大 的 齿 轮, 带 动 那 些 小 的 齿 轮。 但 是, 尽 管 把 一 些 函 数 调 用 放 在 此 处 可 能 比 较 方 便,Windows 编 程 规 则 却 要 求 任 何 响 应 一 个 消 息 的 动 作 都 应 该 放 在 消 息 响 应 程 序 中( 就 是 说, 放 在 窗 口 过 程 中)。 在 一 个 实 时 游 戏 中, 绝 大 多 数 的 动 作 发 生 在 一 个 或 多 个 WM_TIMER 消 息 响 应 程 序 中。 回 合 制 游 戏 则 常 常 在 输 入 消 息 的 响 应 函 数 中 做 大 量 的 工 作。 

……

相关阅读