多任务处理:游戏中的多任务处理

---- Windows 最 杰 出 功 能 的 是 能 够 同 时 运 行 多 个 程 序 但 有 时 也 会 让 人 感 到 头 疼 特 别 是 对 于 那 些 习 惯 于 完 全 控 制 计 算 机 甚 至 时 钟 频 率、 非 常 自 信 游 戏 程 序 员( 当 然 我 们 确 在 乎 那 些 没 礼 貌 、 在 退 出 时 不 恢 复 正 确 系 统 时 间 游 戏 但 是 幸 好 现 在 我 们 可 以 忘 掉 这 些 了)  
---- 在 多 任 务 环 境 下 游 戏 程 序 员 需 要 注 意 3 个 大 负 效 应:  
当 游 戏 失 去 焦 点 而 进 入 后 台 后 其 执 行 不 得 不 被 挂 起( 可 以 在 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) 任 务 优 先 权 我 们 将 在 后 面 会 到 这 个 主 题  
游 戏 循 环  
---- 游 戏 循 环 概 念 在 各 编 程 环 境 下 都 比 较 相 似 需 要 获 取 输 入: 可 以 是 轮 询 它 等 待 它 或 者 在 它“ 运 行” 时 通 过 中 断 或 个 消 息 队 列 拦 截 它 第 2 步 处 理 该 输 入 并 且 把 它 变 成 个 在 游 戏 中 有 实 际 意 义 动 作 如: 使 飞 机 倾 斜 飞 行 或 小 卒 向 前 走 然 后 把 结 果 显 示 出 来 当 然 在 这 个 主 题 中 也 要 求 精 雕 细 刻 和“ 变 奏 曲” 包 括 计 算 AI 移 动、 把 控 制 权 从 个 玩 家 移 交 给 另 个 玩 家、 检 查 胜 负 等 等  

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

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


while (!gamedone)

{

  //时间 -如果时间未到则没有任何响应

  AhabMoved = Move_Ahab;



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

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

   (!AhabMoved) Move_Moby;



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

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

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

    || (AhabMoved))

  {

   UpdateScreen;



    ((MobyX AhabX) && (MobyY AhabY)

      && (paed[MobyX][MobyY]))

   {

    gamedone = 1;

    cprf("\a");

    cprf("You win!");

   }

    (TimesUp <= 0) { cprf("\a"); cprf("Time's up!"); gamedone="1;" } (raw_key="=" MAKE_ESC) { gamedone="1;" progdone="1;" } } //结束更新 } //结束游戏内部循环 (while !gamedone) Moby Dick Windows循环 从表面看来好像没有多大差别: do { (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { (msg.message="=" WM_QUIT) ; //唯退出循环出口 TranslateMessage(&msg); DispatchMessage(&msg); } { ((MobyX !="OldMobyX)" || (MobyY !="OldMobyY)" || (AhabMoved)) { UpdateScreen; ((AhabX="=" MobyX) && (AhabY="=" MobyY) && (paed[AhabY][AhabX])) { Control="MessageBoxEx(hwnd," "You caught Moby! Play again?", "Call Me Ishmael", MB_ICONQUESTION | MB_YESNO, 0); (Control="=" IDYES) InitializeGame; ; } } //如果有人移动了 } //如果屏幕已更新 } //结束循环 while (TRUE);
---- 前 面 已 经 提 到 过 这 里 没 有 检 查 是 否 运 行 超 时 请 忽 略 它 笔 者 在 Windows 版 中 未 实 现 它 是 为 了 避 免 令 人 烦 恼 中 断 中 断 并 退 出 无 限 循 环 机 制 有 点 儿 而 且 并 不 重 要 我 们 把 精 力 集 中 在 消 息 循 环 本 身 所 以 把 其 它 无 关 代 码 都 删 掉 只 留 下 最 基 本 部 分:


do

{

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

{

(msg.message WM_QUIT) ;

TranslateMessage(&msg);

DispatchMessage(&msg);

}

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 时 退 出 该 程 序

msg.wParam;


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

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


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

{

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

TranslateMessage(&msg);

DispatchMessage(&msg);

}

DoSomething;


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

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

延伸阅读

最新评论

发表评论