Programming 版 (精华区)
发信人: superman (☆风雨无阻☆), 信区: Programming
标 题: [转载] DOS到Windows的 游 戏 移 植(2)
发信站: 紫 丁 香 (Sat Aug 29 07:35:39 1998), 站内信件
【 以下文字转载自 cnprogram 讨论区 】
【 原文由 Young_Yang@bbs.ustc.edu.cn 所发表 】
从DOS 到Windows 的 游 戏 移 植(2)
谭 翁
_________________________________________________________________
单 任 务 处 理
---- 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
----
前 面 已 经 提 到 过, 这 里 没 有 检 查 是 否 运 行 超 时, 请 忽 略 它, 笔
者 在 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 消 息 响 应 程 序 中。 回 合 制 游 戏 则 常 常 在 输 入 消 息 的 响 应
函 数 中 做 大 量 的 工 作。
----
事 实 上, 程 序 员 经 常 会 发 现 在 主 消 息 循 环 内, 除 了 标 准 的 翻 译
和 分 发 消 息 任 务 之 外 什 么 事 情 也 没 做。 如 果 这 样, 就 可 以 回 过
头 来 使 用 GetMessage, 因 为 除 了 响 应 消 息 之 外, 什 么 事 也 不 需 要
发 生。
----
总 结 一 下, 实 现 循 环 有 两 个 关 键 点:
* Windows 消 息 循 环 与 游 戏 循 环 不 相 同。 游 戏 循 环 依 然 存
在( 至 少 在 概 念 上 如 此), 但 是 同 DOS 下 的 情 况 相 比,
它 的 部 件 在 代 码 中 更 加 分 散。
* 如 果 想 要 在 消 息 循 环 内 执 行 任 何 独 立 于 时 钟 消 息 和
输 入 消 息 的 代 码, 请 使 用 PeekMessage 而 不 是 GetMessage。
__________________________________________________________________________
--
※ 来源: 中国科大BBS站 [bbs.ustc.edu.cn]
--
※ 转载:.紫 丁 香 bbs.hit.edu.cn.[FROM: poster.hit.edu.c]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:207.571毫秒