• 沒有找到結果。

電腦遊戲程式設計

N/A
N/A
Protected

Academic year: 2021

Share "電腦遊戲程式設計"

Copied!
242
0
0

加載中.... (立即查看全文)

全文

(1)

逢 甲 大 學

資 訊 工 程 學 系 專 題 報 告

電 腦 遊 戲 程 式 設 計

生: 張威(四丁)

授 :林財寶

中華民國九十一年十二月

(2)

目錄

第一章 簡介---1 1.1 我對電玩的想法---1 1.2 電玩的起源---2 1.3 專題動機與目的---3 1.4 遊戲開發流程的簡介---3 第二章 DOS 電玩程式---6 2.1 中斷向量---6 2.1.1 中斷---6 2.1.2 中斷的執行方法---7

2.1.3 BIOS(BASIC INPUT/OUTPUT SYSTEM)---9

2.1.4 在 TURBO C 中如何使用中斷向量和改變中斷向量---10 2.2 螢幕輸出---13 2.2.1 螢幕輸出的原理--- 13 2.2.2 彩色顯像的模式---14 2.2.2.1 16 色顯示模式---15 2.2.2.2 256 色顯示模式--- 18 2.2.3 DOS 遊戲的架構---19 2.2.3.1 遊戲初始化設定---22 2.2.3.1.1 遊戲環境的設定---22 2.2.3.2 遊戲運算和資料處理---26 2.2.3.2.1 主角控制---26 2.2.3.2.2 氣功控制---28 2.2.3.3 螢幕輸出---30 2.2.3.3.1 背景的繪製---30 2.2.3.3.2 主角動畫製作---32 2.2.3.3.3 氣功動畫製作---33 2.2.4 DOS GAME 程式碼---34 第三章 WINDOWS 電玩程式---54 3.1 DIRECTDRAW 物件的建立---55 3.2 告訴 WINDOWS 作業係統遊戲要全螢幕執行---56 3.3 設定解析度和色彩數---57 3.4 顯示頁的建立---57 3.4.1 平面---58 3.4.2 平面建立---58 3.4.3 主要平面(Primary Surface)---59

(3)

3.5 翻轉的觀念---60 3.5.1 何為翻轉---60 3.5.2 建立一個復合平面---61 3.6 電玩程式---61 3.6.1 物件的分類---62 3.6.2 遊戲的架構---63 3.6.2.1 使用者控制輸入部分---67 3.6.2.2 遊戲初始化部分---68 3.6.2.2.1 遊戲環境設定---68 3.6.2.2.2 檔案讀取---69 3.6.2.3 遊戲運算何資料處理---70 3.6.2.3.1 飛機角色控制---71 3.6.2.3.2 敵機角色控制---73 3.6.2.3.3 碰撞檢查---75 3.6.2.3.4 所有精靈的移動---76 3.6.2.4 螢幕輸出---76 3.6.2.4.1 背景的繪製---76 3.6.2.4.2 所有精靈的移動---78 3.6.2.4.3 字幕顯示---79 3.6.2.4.4 主角的動畫製作---80 3.6.3 程式碼---81 3.6.3.1 飛機射擊遊戲的程式碼---81 3.6.3.1.1 input.h---81 3.6.3.1.2 myplane.---81 3.6.3.1.3 myplane.cpp---88 3.6.3.1.4 ddutil.h---103 3.6.3.1.5 ddutil.cpp---105 3.6.3.1.6 ddex4.cpp---135 3.6.3.1.7 resource.h---184 3.6.3.2 地圖編輯器的程式碼---184 3.6.3.2.1 CreateDX.h---184 3.6.3.2.2 CreateDx.cpp---185 3.6.3.2.3 Map_15.cpp---188 3.6.3.2.4 Frame.h---192 3.6.3.2.5 Fram.cpp---193 3.6.3.2.6 SURFACE.h---206 3.6.3.2.7 SURFACE.cpp---207

(4)

第四章 結語---212 4.1 DOS GAME 部分---212 4.2 WINDOWS GAME 部分---212 4.3 對於市面上電腦遊戲的想法---213 4.4 寫程式的學習---213 4.5 飛機射擊遊戲的缺點檢討---214 附錄 A VISUAL C++使用方法簡介---216 A.1 專案開啟---216 附錄 A.2 設定 COMPILER 尋找標頭檔的目錄---222 附錄 A.3 設定 link 所需要用到的函式庫---224 B WINDOWS 飛機射擊遊戲使用方法說明---225 C 地圖編輯器使用方法說明---225 D 參考書目---232

(5)

圖表目錄

圖 1-1 遊戲製作的流程圖---5 圖 2-1 INTERRUPT 圖例---7 圖 2-2 中斷分類圖---7 圖 2-3 利用 debug 查看中斷向量表---8 圖 2-5 中斷向量表圖例---9 圖 2-6 使用 int86()呼叫中斷服務程式---11 圖 2-7 改變中斷向量---13 圖 2-8 螢幕成像掃描---14 圖 2-9 顯示卡資料傳送流程---14 圖 2-10 繪圖模式---15 圖 2-11 16 色螢幕顯像示意圖---16 圖 2-12 16 色顯示模式的硬體線路方塊圖---16 圖 2-13 顯示卡會依照顯色面選擇而將資料存入正確位址---17 圖 2-14 0x03C5 的各位元功能---18 圖 2-15 256 色模式成像顯示原理---19 圖 2-16 DOS 遊戲架構圖---20 圖 2-17 DOS 遊戲初始化設定架構圖---20 圖 2-18 DOS 遊戲運算和資料處理架構圖---21 圖 2-19 螢幕輸出---21 圖 3-1 DDSURFACEDESC 結構---58 圖 3-2 dwFlags 值以及相對應的 DDSURFACEDESC 成員---59 圖 3-3 復合平面---61 圖 3-4 翻轉擁有兩個後緩衝區的復合平面---61 圖 3-5 fire2.bmp---62 圖 3-6 plane2.bmp---62 圖 3-7 valkyrie.bmp---62 圖 3-8 corsair.bmp---62 圖 3-9 battlecruiser.bmp---62 圖 3-10 scout.bmp---62 圖 3-11 wraith.bmp---62 圖 3-12 dropship.bmp---62 圖 3-13 player.bmp---63 圖 3-14 power.bmp---63 圖 3-15 精靈分類圖---63 圖 3-16 飛機射擊遊戲---64

(6)

圖 3-17 遊戲初始畫設定---64 圖 3-18 遊戲運算和資料處理---65 圖 3-19 螢幕輸出---65 圖附錄 A-1---216 圖附錄 A-2---217 圖附錄 A-3---218 圖附錄 A-4---219 圖附錄 A-5---220 圖附錄 A-6---221 圖附錄 A-7---222 圖附錄 A-8---223 圖附錄 A-9---224 圖附錄 A-10---225 圖附錄 C-1---226 圖附錄 C-2---227 圖附錄 C-3---228 圖附錄 C-4---229 圖附錄 C-5---230 圖附錄 C-6---231 圖附錄 C-7---232

(7)

摘要

這一個專題主要是製作出一個電腦遊戲進了解電腦遊戲製作的方法何過程.在第二 章主要是介紹我的一個電玩的失敗品,他是一個 DOS 平台的電玩遊戲,本來打算是 要製作一個角色扮演遊戲的.但是因為只有一個人製作我覺得困難度頗高,所以就 放棄了.再寫這個角色扮演遊戲時有一個比較關鍵的錯誤就是程式的模組化很差, 所有的程式都寫在一個檔案中.之所以會這樣是因為我以前交的程式作業都是在一 個檔案中解決,所以養成了不好的習慣.再來就是並沒有把一些比較主要的功能寫 成函式經由主程式去呼叫.但是雖然我並沒有完成這個角色扮演遊戲但是我還是從 其中學到了很多. 接下來的第三章是最重要的部分.我在 WINDOWS 下完成了一個飛機射擊遊 戲,有了上一次的失誤,在 WINDOWS 下的飛機射擊遊戲我有了更多的經驗.在這一 章中有介紹一個工具函式庫叫做 DIRECTX,可以經由他來完成顯示卡的控制設定 何螢幕的繪圖等等,但是它的功用還不只是如此,比如音效的輸出,音效卡的控制,鍵 盤,滑鼠和搖桿等等的控制它都有提供一些 API 來幫助程式人員撰寫程式. 我想之所以會選電玩製作來當我的專題報告是因為,從小到大玩過很多的電玩. 但是在一開始製作電玩時我並不清楚要如何把教室中老師教的如何拿來應用,這給 我的起頭帶來很大的困擾.再加上寫 WINDOWS 電玩也要對 WINDOWS 程式設計 有一些了解....但是對於只學過 TURBO C 的我來說第一次看到 WINDOWS 的程式 真的覺得很怪....而且有一個很大的錯誤也是困擾了我很久.剛開始我想學一些有關 WINDOWS 程式設計的時候,我找出了一本我大 2 下學期的選修課視窗程式設計的 書來看.沒有錯,它的的確確是教人 WINDOWS 程式設計的書.可是它是教人如何使 用 MFC 來視窗程式設計的書.MFC 是一個很不好的學 WINDOWS 程式設計切入 點,MFC 是把 WINDOWS 作業系統提供的 API 包裝起來寫成類別讓使用者呼叫加 上一大堆解釋的很奇怪的名詞,真的讓人覺得很鬱悶....但是我一開始並不知道這個 情況我以我還不只買了一本 MFC 的書,我總共買了 3 本可是都一直看不太懂.還好 後來我改變了方向我買了一些用 WINDOWS API 寫程式的書,這些書我比吸收....所 以我有了進一步的開始.接下來的另外一個難題是我根本不知道什麼是 DIRECTX, 我只是常常在 BBS 板上和遊戲設計的網站上看到這個字眼.後來我買了 DIRECTX 實技和 DIRECTX 發展手冊這兩本書,老實說這兩本書我也都很難看的懂.可是我還 是得想辦法弄懂 DIRECTX 是為何物呀,最後我選擇了去讀微軟網站 DOWNLOAD 下來的 DIRECTX 說明文件和範例.我把說明文件和範例一起看.居然很容易的就被 我弄懂了一些 DIRECTX 的基本應用,原本我是因為不想去看那一些英文的 DIRECTX 說明文件的....可是它居然可以讓我很容易的了解.我自認為英文不好,每 一次老師選擇英文課本時,我總會去找中文翻譯.這一次我卻是讀 DIRECTX 的英文 技術手冊才了解如何使用 DIRECTX,我自己也覺得很奇怪.

(8)

這兩大難題後,我終於可以開始寫我的電玩了....雖然這兩大難題並不是我製作過程 中所遇到的最大 TROUBLE,但是卻讓我難以起頭.正所謂萬事起頭難,看來所言不 假.

(9)

第一章 簡介

1.1 我對電玩的想法

在我第一次接觸電動玩具時記得應該是小學 2 年級 bah。那時是任天堂紅白機 的時期…. 。在那一款主機上最讓我著迷的是三國志三代,它是一個策略模擬遊戲 可以讓你當一個國家的皇上來管理你的國土,或著是對他國發動攻擊。接下來我 所擁有過的主機還包括超任(超級任天堂)、PS(SONY PLAY STATION) 、SS(SEGA SATURN)這一些主機陪伴我度過了許多孤獨的時光,在國中的時候那時我很迷大 型電玩機台常放學後跟同學去電玩店打電玩。直到高二時家中買了一台電腦我發 現電腦遊戲更是十分的吸引我如魔獸爭霸 2 、紅色警戒、是我第一跟第二個在電 腦上玩過的電動玩具。可是雖然我玩過那麼多電玩但是卻未曾深思到底一個電動 玩具要如何製作,直到我選擇電腦遊戲當我的作業我才來思考這個問題…. 仔細回想我所曾經讀過的科目好像都跟電玩設計沒有多大的關係,其實多想 一想還是有關的比如說一個超級任天堂的主機它也有自己的 CPU(雖然我不知道是 什麼型號的 CPU) 。既然有 CPU 那應該就有那個 CPU 的指令集,所以我想當要製 作一個以超級任天堂為平台的遊戲就要用超任的 CPU 的指令集來撰寫程式。可是 CPU 的指令集都是 0101 的機械碼,所以根本粉難用 CPU 的機械碼來直接編寫超 級任天堂的程式。於是乎任天堂公司也寫了一個組譯器使得程式人員可以用更高 階的語言來寫超任的程式,所一這就跟學校教的 system programming 和 compiler 有關。事實上當在比較早期的電玩主機(任天堂紅白機或著是超任)上寫遊戲人們可 以直接的控制硬體接觸到最底層,比如說要作螢幕輸出時那你就要在你的程式碼 中加入一段把要顯示的資料輸出到遊戲機的顯示記憶體等等之類的。後來更先進 的電玩主機就有一些類似電腦中的 system call 之類的 procedure

已經事先寫好,然後遊戲設計的人就可以在寫遊戲的過程中呼叫別人已經先寫好 用來控制電玩主機低階硬體的 procedure(這就好像是我們在 DOS 環境下寫 X86 組 合語言時可以呼叫中斷服務程式一樣的感覺) 。然而更更新的電玩主機誇張到有自 己的作業系統,如 MICROSOFT 出的 XBOX 就是一個例子。 而我自己再電玩實作方面是選擇了以 MICROSOFT 的 WINDOWS98 為遊戲平 台來完成我的電玩。我也有寫了一個以 DOS 為平台的半成品,我希望以後也有機 會接觸要如何在其它的遊戲主機上發展電玩程式。我想隨著時代的進步電玩也是 越來越花樣百出。21 世紀隨著網路日漸普遍讓世界各地的人能藉由網路交流是新 的趨勢,電玩也是如此。還有有一些遊戲有用到 3D,但是我只是一個初學著所以 我的電玩成品是一個 2D 飛機射擊遊戲。但我在實作的過程中還是學到了很多,這 個 2D 飛機射擊遊戲對我自己來說只是一個學習的開始我以後還想學習網路和 3D

(10)

遊戲的製作,這就是我對電玩的新的想法Î用一個嶄新的方式來”玩”電玩。

1.2 電玩的起源

要說起這電腦遊戲史,那可是說長也不長、說短也不短。當年 COMPUTER 出現時,人們根本也沒想到用她來進行娛樂。可那些整天被困在那些笨重冰冷的 巨大怪物前面的工作人員,為了緩解工作壓力(說穿了就是偷懶)就利用職務之 便與專業特長編制了一些諸如猜數這樣的趣味小程序來自己娛樂一翻。此時,電 腦遊戲這樣一顆極為弱小卻又蘊藏了無限生機的種子被種下了。 隨著 1970 年最古老的八位個人電腦––蘋果公司的 APPLE 系列機的出現, 電腦的附屬娛樂價值也隨之進入了市場。最先出現的遊戲都是一些十分簡陋的動 作、射擊類遊戲,諸如《金錢豹》、《不死鳥》、《決戰富士山(KRATEKA)》。而當 這些在今天看來土著到了極點的作品出現在 APPLE II 著名的綠色屏幕上時,人們 卻如同發現了新大陸般的驚喜不已––原來電腦的娛樂功能竟讓人如此沉迷,於 是電腦遊戲的具雛形階段也就開始了。 而在這一時期,十分有趣的一件事就是這些隻有幾十 K 的小程序竟是以數位 訊號的方式記錄在錄音帶上的。初期 APPLE II 隻有 48K 的記憶體(後期的 APPLE II 可達 128K)、錄音帶的容量又十分的有限,這些阻礙了電腦遊戲的發展。因此當 時成功研制的大容量磁道使得許多原本受限於技術的不可能成為了可能。電腦遊 戲的規模數量不僅有了巨大的進步,遊戲類型除了原來單純的動作、射擊外,策 略、冒險、角色扮演、戰爭、體育等類型也出現了。1979 年 SIR-TECH 推出了電 腦遊戲史上第一個角色扮演類遊戲《巫術》(WIZARDRY)。它的出現意味著 APPLE II 的黃金時代也隨之拉開了帷幕。在這十年輝煌裡,湧現出了如《巫術》、 《創世紀》(ULTIMA)等許許多多令老玩家畢生難忘的經典之作,許多現在的大 遊戲公司也是在這一時期出現、發展的。然而更為重要的是如今大部分的遊戲概 念、認識都是在這個時期裡成熟起來的,因此這個時期被稱為電腦遊戲史上最重 要的時期一點也不為過。當時間車輪轉到了 80 年代末期,IBM 公司的 PCXT 出現 了。它具有 286K 記憶體以及其他超過 APPLE II 的硬件能力。再加上 APPLE 公司 在其後繼電腦產品上采取了技術封閉的錯誤方針,導致眾多遊戲廠商紛紛轉投采 用開放技術的 IBM 的懷抱。APPLE 的市場便漸漸為 IBM 所吞食,最終在 90 年代 我們迎來了 IBM PC 時代。雖然在這場電腦史上最重要的交戰中 IBM 成了贏家, 但眾多的電腦玩家卻也成了間接的獲益者。隨後的事大家也應該都有所聞有所見 了:從 PCXT 到 AT286、386、486 直至 PENTIUM;從單色顯示到四色 CGA、256 色 VGA 直至 SVGA;從 360K 的 5 寸盤到 1﹒44M 的 3 寸盤、650M 的光盤直至 2G 的硬盤;從 DOS3.0 到 DOS6.22、WINDOWS3.1 直至 WINDOWS95。電腦可以 說是以幾何級數的發展速度迅速向前推進;多媒體的出現使遊戲的聲光效果達到 了十年前人們無法想像的高度;INTERNET 使得全世界的電腦可以自由聯接,人

(11)

與人之間的較量使得電腦遊戲不再是人與機器間的對抗。總之,這個時代是電腦 遊戲正向著極致發展的時代。

1.3 專題動機與目的

因為我從小到大玩過很多電玩一直到讀了資工系所以想說對於如何寫電玩程 式有興趣而且我以後也有想要朝這個行業發展,所以電玩設計為專題的題目。 同時也可以磨練寫程式的能力。但是我本來想要在遊戲的程式中看看是否能某些 部分用 X86 的組合語言寫。無奈功力未達所以只好放棄,但是我一值認為組合語 言對於電玩設計有其重要的地方。因為組合語言是速度最快的而電腦遊戲又是十 分講求效能的程式,所以在寫程式的過程中可能有某個函式的運算特別花時間 。此時就可以考慮用組合語言。 另外我的電玩專題實作部分主要是希望做出一個大概具有一些基本電玩功能 的遊戲,並不要求是一個規模龐大的電玩軟體,但是具備完整性。什麼又是我所 謂的完整性呢?打個比方來說,如果要製作一個類似日商光榮公司出品的三國志 8 這種類型的策略遊戲,你的遊戲就必須要能讓玩的人可以經由你所提供的介面達 到管理你自己領土的政治、軍事、農業、經濟等等事項的功能,和戰鬥等等的功 能…..我覺得對我來說一個人要做那麼多很難更何況還要準備要用到的圖檔 文件檔等等的雜務。因此我的遊戲專題分為兩大部分第一個部分是我想要在 DOS 環境下製作一個 RPG 遊戲,但是我並沒有完成,也就是說是一個未完成品。 後來我選擇在 WINDOWS 環境下從新製作一個飛機設擊遊戲。這個遊戲就比較可 以說是一個完整的遊戲,畢竟 2D 飛機射擊遊戲的製作方面在許多遊戲分類方面算 是要求比較少的,當然啦….如果你硬是做一個超級複雜的飛機射擊遊戲也是可以 的….

1.4 遊戲開發流程的簡介

真正完整的遊戲製作必須經過相當繁複的過程,以及投入相當的人力和物力 來開發,是需要團體合作的。但是大概可以分成下面幾個步驟…. A 構思 企劃人員思考了許多的構想,在其中只選擇好的構想。也就是說在一個遊戲 的產生之前,有相當多一般人看不到的構思存在。 構思完成以後就是把你的想法寫下,寫成正式、規格化、易於查閱的文書(也 就是企劃案)。 B 撰寫企劃案 企劃案是將遊戲的構思加以整合而撰寫出來的文件。它可以包含有遊戲的畫面草 圖、操作方法、遊戲的流程、遊戲的內容、需要用到的資料等等….

(12)

以企劃案做為根本來考量實際遊戲的製作與否,遊戲是要照著企劃案所撰寫 的內容進行,還是做部分的修改刪減。所以企劃案是為了要表達此遊戲的樂趣的 重要方法,在任何遊戲都是不可或缺的重要文件。 而且企劃案再多人團隊合作時更是特別重要。工作的分配協調都要常常參考 到它,但是企劃案並不是佛門寶典一字不得跟改的…. 接下來的工做是進行實驗。 C 進行實驗 招集程式、美術以及其它的工作人員如音樂等等的…依據企劃案的內容進行 實驗,評定此一企劃案是否可行。 D 修改計劃書和定立資料規格 此一階段也可以稱之為「遊戲原型製作」,它的目的是用最快最簡單的方式來 呈現遊戲的雛形,從而預計出整個遊戲的整體風格、遊戲性、可行性。 E 輔助工具的製作 這個步驟就是製作一些地圖編輯器、劇情編輯器、一般常聽到的所謂 3D 遊戲的人 物編輯器等等的都是在這個步驟製作。 F 音樂、音效、美術、程式的製作 在撰寫企劃書和定立資料格式的同時,開始製作程式的核心部分。在完成核 心部分的時候,進行遊戲構思之程式表現。程式人員在此一階段中,須要進行的 工作相當繁複。除了程式本體的核心部分需要架構之外,還需要緊密的與美術、 音樂、音效、企劃人員進行溝通。最好能明定出許多系統上限範圍,讓製作小組 其它成員能夠了解,自己的工作需要在什麼樣的可行範圍去發揮。 在又係完成一階段(單元) ,音樂製作人員依據遊戲場景製作背景音樂,在依 據各部動作製作音效。 G 完成遊戲和測試錯誤 上面的步驟都可行的話則可以完成遊戲。可是遊戲的完成還要測試是否有錯 誤(通常最大的可能是找出程式上的錯誤) ,然後不斷的根正。就那我熱愛的即時 戰略遊戲 EXPANSION SET OF STARCRAFT:BROOD WAR(由 BLIZZARD 製作) 是一款五年前的遊戲但是經過不斷的改版更正現在還是有程式上的錯誤。 以上 A 到 G 的步驟我自己在實作遊戲時我是完全不管的,一方面是我的遊戲 只有我一個人製作一方面也是因為我覺得也沒有事先規劃遊戲的架構內容等等也 沒關係,當然產生了粉多不良的後遺症。但往好的方面想卻也得到了經驗,我將 再專題檢討的部分更進一步的說明。 以下乃是遊戲製作的流程圖

(13)

圖 1-1 遊戲製作的流程圖 構思 撰寫企劃書 進行實驗 修改計劃書和定立資料規 格 音樂、音效、美術、程式 的製作 量產和除錯 輔助工具的製作

(14)

第二章 DOS 電玩程式

這一章主要是介紹一些寫 DOS 電玩程式的基本方法。我自己覺得寫 DOS 電 玩程式和寫 WINDOWS 電玩程式最大的的不同點在於,DOS 電玩程式寫程式的人 要自己寫控制低階電腦硬體的程式而 WINDOWS 程式卻不用(也不准)程式人員這 樣做。 我的 DOS 電玩是用 TURBO C 來當編譯器的,而且並沒有完成它。

2.1 中斷向量

2.1.1 中斷

80X86 CPU 與其它各種的 CPU 都具有中斷(INTERRUPT)的能力。當中斷訊號 出現時 CPU 就會中斷目前的工作而去執行中斷所指定的程式,這種由中斷指定執 行的程式就叫做中斷服務程式(INTERRUPT SERVICE ROUTINE)又簡稱為 ISR。當 ISR 執行完畢,CPU 又會回頭繼續執行剛才被中斷的程式,所以 ISR 與一般的副 程式有些類似。然而由於其性質比較特殊,ISR 和一般的副程式也有些不同。 所謂 INTERRUPT 在我們一般生活中也常常發生。比如當你正在修車子時, 突然間你的手機響了,所以你就接聽你的手機跟別人獎電話,當講完電話時你又 要繼續修車。電腦中的 INTERRUPT 就很類似於這種情況。下面是 INTERRUPT 的一個圖例 中斷 A 服務程式 中斷 A 中斷返回(IRET) 中斷 B 中斷 B 服務程式 中斷返回(IRET)

(15)

圖 2-1 INTERRUPT 圖例 中斷的來源有兩種,一種來自於硬體一種來自於軟體。硬體的中斷可能來自 於 CPU 內部或著是外部的中斷設備,而軟體的中斷則如同一般的指令一樣,以指 令的形式存在於一般的程式片斷當中。 當 CPU 執行到某一種特殊狀況時,會自動執行中斷的動作。這種中斷叫做 CPU 內部的中斷,例如當 CPU 發生除以 0 的運算時,便會執行第 0 號中斷。此外第 1 號和第 4 號中斷也是 CPU 內部的中斷。當初 Intel 公司是保留 INT 0H~

INT 1FH 為 CPU 內部的中斷,但是 IBM 公司還是使用了小於 1FH 的中斷。外部 的硬體中斷是指 CPU 外部的設備(如:磁碟機、硬表機、鍵盤….等等)對 CPU 所要 求的中斷,當外部硬體發出中段時,CPU 會視當時的 IF(INTERRUPT FLAGE)的 值來決定是否執行中斷服務程式,必須 IF=1 時才可以執行中斷。IBM PC 是一個 以 Intel 的 8259 中斷控制器來處理同時發生多個硬體中斷的問題。8259 會一個硬 體的優先權來處理同時到達的中斷要求,並且選擇優先權最大的對 CPU 發出中斷 請求。 以下是一個中斷的分類圖。 圖 2-2 中斷分類圖

2.1.2

中斷的執行方法

80X86 是以號碼來指定中斷服務程式。例如 INT 21H 就是指定 80X86 中斷目 前的工作而去執行第 21H 號中斷服務常式。但是只給號碼你是無法期待 CPU 去執 行中斷服務程式的,CPU 必須知道中斷服務程式的起始位址才可以執行中斷服務 中斷 軟體中斷 硬體中斷 DOS BIOS 應用程式 CPU 內部 CPU 外部

(16)

程式。事實上在 ROM 的 00000~003FF 的記憶體未指中存放著每一個中斷服務程 式的起始位址,每一個中斷服務常式的位址用 4 個位元組來存,所以第 0 號中斷 服務程式的位址在 00000~00003,第一號的中斷服務程式的位址在 00004~00007…. 。第 n 號的中斷服務程式的起始未只是存於中斷向量表第 4n 位元 組上。當 CPU 執行到第 n 號中斷指令時,便由向量表第 4n 位址上取得中斷服務 程式的起始位址而往服務程式去執行。 我們可以在 DOS 模式下鍵入 debug 指令,來看一看中斷…. 圖 2-3 利用 debug 查看中斷向量表

上途中我們可以看到在 DOS 環境下鍵入 debug 接著再鍵入 a(鍵入 a 表示我們 要編寫組合語言)接著我們可以看到一行 126B:0100 int 21:這一行表示我們在記憶體 126B:0100 的位址填入了 int 21 的組合 語言指令。接著下一行……. 126B:0102:這一行我們什麼都沒寫然後按 enter 鍵就可以退出組合語言編寫模式。 順便一提 int 21 這個組與指令轉成 CPU 可讀的機械碼其長度是 2,因為它存放在 126B:0100~126B:0101 之間。 再來我們鍵入 t(TRACE 模式)表示我們由 126B:0100 開始執行程式而且一次只 執行一個組合語言指令。而且每執行一次組合語言指令都會列出 CPU register 的 值,所以我們可以看到在執行完 int 21 這一指令後 CPU 的 CS=08C2、IP=0445。 而 CS 跟 IP 所儲存的分別代表 CPU 所要執行的下一個指令的節位址和節內位址(這 一句似乎有點饒舌….@@) 。再來我們按 q 退出 debug…..

(17)

接著在 DOS 命令提示字元後鍵入 debug(重新進入 debug) ,接著鍵入 d 0000:0084H。這一行告訴 debug 說我們要你 show 出記憶體位址 0000:0084 以後的 記憶體內容,記得嗎?前面說過當 CPU 執行到第 n 號中斷指令時,便由向量表第 4n 位址上取得中斷服務程式的起始位址而往服務程式去執行,所以第 21H 號的中 斷服務程式的起始位址儲存在記憶體位址 0000:0084H(21H 轉成十進位等於 33,33×4=132,132 的 16 進位是 84H)。接著神奇的是發生了…. ,由上圖中可以看 到,由 0000:0084~0000:0083 的內容是 45 04 C2 08 這正好和乎執行完 int 21 這一指 令後 CPU 的 CS=08C2、IP=0445(因為這是小終系統)。 下面是中斷向量表的圖例。 00000H 第 0 號中斷服務程式位址 00004H 第 1 號中斷服務程式位址 00008H 第 2 號中斷服務程式位址 0000CH 第 3 號中斷服務程式位址 4 位元組 第 n 號中斷服務程式位址 圖 2-4 中斷向量表圖例 user program INT 21H 45 00084H ‘ 04 ‘ C2 ‘ 08 圖 2-5 中斷向量表圖例

2.1.3 BIOS(BASIC INPUT/OUTPUT SYSTEM)

PC 上的 BIOS 是一個由 IBM 公司提供的基本輸出輸入系統。它提供了許多控 第 21H 中斷服務 程式

(18)

制程式,用來幫助使用者控制輸入輸出的硬體部分;它也包含了一些副程式,用來 處理一些錯誤的情況發生。既然 IBM 提供了這一些常氏,我們可以拿來利用,實 在沒有必要自己再去寫一份控制程式。使用 BIOS 及 DOS 的系統服務可使程式簡 化許多。 使用 BIOS 的控制程式必須經由一定的標準呼叫方式,因此在程式中使用 BIOS 來控制硬體有一點很大的好處,就是程式的可攜性(PORTABILITY) 。硬體 的裝置可能會隨著時間的變化而有所改進。而當硬體改變時製造者也會自動修改 BIOS 的控制程式來配合,並維持呼叫的方法不變。因此即使新機器的硬體改變了, 你原有的程式不必修改也能夠在新機器上正確無誤的執行。

2.1.4

在 TURBO C 中如何使用中斷向量和改變中斷向

其實跟我們在組和語言中使用中斷向量跟改變中斷向量的方法很像。TURBO C 為了讓人在 TURBO C 中使用中斷向量,所以他在標頭檔中定義好了有關存取 CPU 內的 REGISTER 的 structure 和 union。

就如下面所列出來的。 /*word registers*/

struct WORDREGS {

unsigned int ax; unsigned int bx; unsigned int cx; unsigned int dx; unsigned int si; unsigned int di; unsigned int cflag; } ;

/* byte registers*/ struct BYTEREGS {

unsigned char al,ah; unsigned char bl,bh; unsigned char cl,ch; unsigned char dl,dh; };

(19)

/*general purpose registers union overlays the corresponding word and byte register.*/ union REGS { struct WORDREGS x; struct BYTEREGS h; }; /*segment registers*/ struct SREGS {

unsigned int es; unsigned int cs; unsigned int ss; unsigned int ds; };

接著 int86()這個 function 能讓 C 語言的程式設計者,直接呼叫 BIOS 的副程式。 ROM BIOS 副程式是由中斷(INTERRUPT)的方式來存取,當我們在 C 程式中呼叫 ROM BIOS 的副程式時,可以將 INTERRUPTS 的動作即交由 function int86()來執 行。 下面是一個在 C 程式中使用 function int86()的圖例。 ROM-BIOS 中斷副程式 void main(void) { // C program // C program // C program int86(); // C program // C program // C program } 圖 2-6 使用 int86()呼叫中斷服務程式 在使用 function int86()時,要遵循它的使用方法:

(20)

*output_registers) interrupt_number:ROM-BIOS 中斷編號。 input_registers:呼叫前的 register 存放位址。 Output_registers:呼叫後的 register 存放位址。 就跟我們在組合語言中使用中斷服務程式一樣,當我們呼叫中斷服務程式之 前我們要把一些中斷服務程式執行時所需要用到的參數存到某些特定的 REGISTER 中,若中斷服務程式有回傳值它也會把回傳值存放於某些特定的 REGISTER 中。 另外 TURBO C 中還提供了別的方法可以很容易的呼叫中斷服務程式。 那就是利用 PSEUDEVARIABLES。那就是…. _AX,BX,_CX,_DX,__AH,_AL,_BH,_BL,_CH,_CL,_DH,_DL, _SI,_DI,_BP,_SP,_ES,_SS,_CS,_DS。你可以在你的 C program 中的任何 地方使用,用來直接存取 CPU REGISTERS。另一個函數 geninterrupt(int

interrupt_number)也可以用來執行中斷服務程式,geninterrupt()可以跟 PSEUDEVARIABLES 配合來執行中斷服務程式。 接下來要如何改變中斷服務程式呢?假如你在 DOS 命令提示字元下鍵入一個 a 鍵,那你就會在螢幕上看到列印出一個’a’字母。可是我們在遊戲中是否可以改變 鍵盤中斷呢?比如我再遊戲中如果按了一個 a 鍵那其實是發一個氣功呢?可不可以 這樣做?答案是可以的。要達到這個效果我們必須改變鍵盤的中斷向量。 我們隻到當使用著每按一次鍵盤就會觸發一次 int 09H(由外部硬體觸發的中 斷)的中斷,int 09H 中斷服務程式會由 8255 的 60H I/O 位址讀取按鍵訊號(scan code) ,然後把該鍵的 ASCII code 和 scan code 分別先後存到 0041:001E~0041:003E 的記憶體 buffer 中。 而中斷 int 09H 的起始位址存在中斷向量表中,它存在由 0000:0024H(09H×4H=24H)~0000:0027H 的記憶體區塊中。所以如果我們可以改變 00024H~00027H 這一段記憶體區塊內的值那是否就表示當我們按下鍵盤時,一樣 會由外部硬體的鍵盤來觸發 int 09H 中斷服務程式。但是這一次 int 09H 中斷服務 程式的起始位址改變成另一段你自己寫的程式在記憶體中的起始位址。於是乎, 神奇的事情發生了,當你按下鍵盤時電腦不會在螢幕列印出你按的字元,而是做 其它你指定要做的事。

上面的想法我們可以在 TURBO C 中的 setvect(int interrupt_number,void interrupt far * a_interrupt_function_pointer)和 getvect(int interrupt_number)加以實 現。getvect()的回傳值是你所傳入的中斷向量的遠程指標,setvect()的功能是把 int interrupt_number 中斷服務的起始位址設成 a_interrupt_pointer 所指的位址,而 a_interrupt_pointer 所指的位址正式你自己寫的中斷服務程式的起始位址(….好饒舌 ya….) 。

(21)

下面是改變中斷向量的圖例。 這裡使用者 新的中斷向 改變了中斷 量起始位址 向量 圖 2-7 改變中斷向量

2.2

螢幕輸出

2.2.1 螢幕輸出的原理

想想看一張彩色照片是如何表現你所拍下的景象的呢?當我們注視相片上的 人物、風景時其實相片上的圖案是由一個個的小點所拼湊而成。由於這一些小點 排列緊密所以在人眼中看起來是一幅完整的圖案。事實上拍照時所使用的底片上 有許多微小但是可以感應色彩的化學分子顆粒,當按下快門的那一剎那,這伊些 感光的分子起了化學變化保留了瞬間的影像。之後,再由洗照片的機器把這一些 影像顆粒還原成色彩,印在照片上時就成了我們看到的圖案。 螢幕的顯示也是利用相同的道理產生影像。顯示卡將 PC 圖形資料轉換成螢幕 能接受的類比訊號,控制其內部的電子槍,將電子打到顯示幕上來產生一點一點 的顏色。由於其速度很快讓人看起來像是衣服完整的圖案。如圖 pic.2-8 所示,電 子槍由左而右,由上而下一列一列的”掃描”圖形小點,每一小點稱之為”像 點”(PIXEL) 。當掃描完整個螢幕時電子槍由回到螢幕的最左上角重新掃描一次整 個螢幕。 當 PC 要顯示畫面時,會把要顯示的資料存到顯示卡的記憶體上,顯示記憶體 儲存了資料以後顯示卡在將資料轉換成類比訊號交由螢幕顯示,就如同圖 2-9 所示 的一般。

(22)

圖 2-8 螢幕成像掃描 螢幕 圖 2-9 顯示卡資料傳送流程

2.2.2

彩色顯像的模式

PC 的螢幕顯示分成兩大模式,那就是文字模式跟繪圖模式,文字模式只能顯 示文字(ASCII 符號) ,不能繪圖,繪圖模式則是兩者都可一顯示。一般狀況下, PC 處理的是文字模式,這個模式所顯示的文字和特殊符號的樣子都事先儲存在顯 示卡的唯讀記憶體(ROM)中。PC 只要將這一些文字和特殊符號的 ASCII 碼寫入顯 示記憶體中,顯示卡會自動依據 ASCII 碼查出這個字的顯示圖案,然後控制電子 槍做掃描把它顯示在游標所指的位址。 由於文字模式把螢幕分成 25 列 80 行(直行橫列)。所以在一列中最多只可以顯 示 80 個字姆,整個螢幕最多只能塞下 25 橫列。整個螢幕好像為 25×80 的棋盤方 格,因此我們不能在文字模式下把點化到任意一個位置上,只能在 25×80 的方格 中顯示一個因文字姆或符號。 但是繪圖模式底下允許我們把一個一個的像點描繪在螢幕的任一位置之上, 依照顯示卡功能的不同繪圖模式還可以細分為下面的模式。 (1) 把螢幕分成 320×200 個像點,就是水平 320 個像點,垂直 200 個像點的解析 數位轉類 比 顯示記憶 體

(23)

度。 (2) 把螢幕分成 640×200 個像點的解析度。 (3) 把螢幕分成 640×350 個像點的解析度。 (4) 把螢幕分成 640×480 個像點的解析度。 (5) 把螢幕分成 640×600 個像點的解析度。 (6) 把螢幕分成 640×768 個像點的解析度。 其中的(1)~(4)項為 IBM PC /AT 的標準規格。而每一種解析度又可以分為不同 的顯示顏色數如同下圖。 解析度 顏色數 640×200 2 320×200 16 640×200 16 640×350 4 640×350 16 640×480 2 640×480 16 320×200 256 800×600 16 800×600 256 1024×768 16 1024×768 256 圖 2-10 繪圖模式

2.2.2.1

16 色顯示模式

在 IBM PC/AT 當中顏色是由 R、B、G、I,等是個元素共同組成的,因此, 我們可以將一格螢幕想像成如圖 pic.2-11 所示的結構。圖中一個畫面一分為四,每 一個像點由四個顯色面所組成。顯色面 0 代表藍色、顯色面 1 代表綠色、顯色面 2 代表紅色、顯色面 3 代表亮度。顯示卡的顯示轉換電路會將這 4 個面的電路一起 讀出,然後將之和成為一個顏色。在 16 色顯示模式下每一個像點要以 4 個位元(bit) 來表示其顏色(2^4=16) ,也即是每一個位元表示一個顯色面的資料。因此硬體的 設計很自然的把這四個顯色面得資料存在 4 組不同的記憶體中,每一組記憶體代 表一個顯色面,就如圖 pic.2-12 所示。這樣的設計可以使得硬體線路能同時對 4 組記憶體發出控制訊號,因而能在最短的時間內同時取出 4 個顯色面的資料加以 顯示,如此才能來的即顯像而不至於產生閃爍的現象。

(24)

顯色面 3 代表亮度 顯色面 2 代表紅色 顯色面 1 代表綠色 顯色面 0 代表藍色 圖 2-11 16 色螢幕顯像示意圖 B 資料 (顯色面 0) G 資料 (顯色面 1) R 資料 (顯色面 2) I 資料 (顯色面 3) 圖 2-12 16 色顯示模式的硬體線路方塊圖 但是因為同一個像點的資料被安排在 4 個不同的記憶體區塊中,所以當我們 要顯示某個像點時,必須對 4 個顯示記憶體分別寫入 R、G、B、I 等 4 個資料,換 句話說必須執行 4 次的寫入動作。 可是 PC 的顯示記憶體是配置在 A000:0000~A000:FFFF 的 64Kbytes 記憶體, 且這 4 組記憶體的總容量尚且遠遠超過 64bytes(現在大多配置 1024K bytes)的記憶 體。64KB 的 PC 位址空間如何對應到顯示卡的顯示記憶體呢?原來顯示卡上有一個 選擇顯射面的暫存器,必須先選擇好要寫入的顯色面才能夠把真正的顏色之料存 入…. 可以將寫入的動作分解如下。 (1) 首先告訴顯示卡,我們要選擇顯色面了。 (2) 選擇顯色面 0。 (3) 把藍色(B)資料寫入相關位址。 (4) 選擇顯色面 1。 (5) 把綠色(G)資料寫入相關位址。 資料 存取 電路 數位 類比 轉換 (D/A) 類比 訊號

(25)

(6) 選擇顯色面 2。 (7) 把紅色(R)資料寫入相關位址。 (8) 選擇顯色面 3。 (9) 把亮度(I)資料寫入相關位址。 但是為什麼 4 次寫入的動作都寫入相同的位址呢?那是因為顯示卡有一個電路 會依據我們所選擇的顯色面而把不同的色元素寫入不同的記憶體中,因此即使我 們把資料寫到相同的位址,但因為顯色面的不同,顯示卡會自動將之存入正確的 記憶體位址。 顯示卡 圖 2-13 顯示卡會依照顯色面選擇而將資料存入正確位址

前面講的步驟,在 TURBO C 中可以 outportb()和 memset()來完成: 步驟(1):outportb(0X03C4,0X02);準備做顯示記憶體輸出

(2):outportb(0X03C5,0X01); 選擇顯色面 0 (3):memset(ptr,B_DATA,1);將值存入 vedio ram (4):outportb(0X03C5,0X02); 選擇顯色面 1 (5):memset(ptr,G_DATA,1);將值存入 vedio ram (6):outportb(0X03C5,0X03); 選擇顯色面 2 (7):memset(ptr,R_DATA,1);將值存入 vedio ram (8):outportb(0X03C5,0X04); 選擇顯色面 3 (9):memset(ptr,I_DATA,1);將值存入 vedio ram 註:ptr 為指向 vedio ram 任一位址的指標。 註:B_DATE、G_DATE、R_DATE、I_DATE,則分別代表要送往顯色面 0、1、2、 3 的資料。 PC 資料 資 料 選 擇 G 資料 B 資料 R 資料 I 資料

(26)

I/O 位址 0X03C4 為顯示卡上的暫存器,其內容為 2 表示接下來要設定顯色面, 決定顯色面的暫存器位址為 0X3C5,它的位元 0 為 1 表示現在選擇顯色面 0、它的 位元 1 為 1 表示現在選擇顯色面 1、它的位元 2 為 1 表示現在選擇顯色面 2、它的 位元 3 為 1 表示現在選擇顯色面 3,就如下圖一般…. Bit 7 6 5 4 3 2 1 0 × × × × 位址 0x035C 控制顯色面 0 控制顯色面 1 控制顯色面 2 控制顯色面 3 圖 2-14 0x03C5 的各位元功能 在已經知道 16 色顯示模式的控制方法後,接下來就是要知道螢光幕上的像點 跟顯示記憶體的對映關係。 回想看看 16 色可以搭配許多不同的解析度如 320×200、640×200、640×350、 640×480 等等,由於它們的原理都相似,因此就以 640×480 為例,說明像點位址與 顯示記憶體的對映關係。 比如螢幕上第 45 列第 125 行的點它是對映到哪一個記憶體位址的第幾個 bit 呢? 因為每一列有 640 個 PIXEL,而每一個 PIXEL 的紅色資料、綠色資料、藍色 資料、亮度資料各用 1bit 表示。所以第 45 列第 125 行的點的紅色資料、綠色資料、 藍色資料、亮度資料的位址在 A000:0E1F(45×640+125 除與 8 的商是 3615, 46×640+125 除與 8 的餘是 15,3615 的 16 進位是 0E10,15 的 15 進位是 F) 。

2.2.2.2

256 色顯示模式

雖然 256 種顏色比 16 種顏色多但是我自己卻覺得 256 色比 16 色更容易懂。 PC 在這一種顯示模式下每一個點可以有 256 種顏色,正好是 2 的 8 次方。所以在 這個顯示模式下以一個 byte 來表示螢幕上的一個點是十分合理的想法。以 320×200 的 256 色顯示模式為例,因為螢幕上的一個像點由 1 個 byte 來表示它的顏色,所 以螢幕想點與記憶體是一對一的線性關係。比如螢幕上第 45 列第 125 行的點,它 在記憶體中的位址是 A000:70FD(45×640+125=28925=70FDH) 。而在 320×400 的 256 色顯示模式下整個螢幕的像點總共有 640×480 個像點,每一個像點 1byte 所以 共是 64KB 的大小。另外其實 256 模式下每一個點是由一 byte 來表示但是,這個 byte 的值其實是一個顏色索引值,在顯示卡中有一個顏色暫存器它有 256 個儲存

(27)

位置,每一個位置的大小是 3byte,每一個位置存的值代表真正的顏色值。舉例來 說如果比如螢幕上第 45 列第 125 行的點,它在記憶體中的位址是 A000:70FD,那 當顯示卡要顯示其顏色時就要看 A000:70FD 所存的 byte 的值是多少。比如是 123, 那顯示卡就要從顏色暫存器第 123 個儲存位置抓 3bytes 的資料來表示螢幕上第 45 列第 125 行的點的顏色。所以如果我們改變顏色暫存器第 123 個位置的 3 個 bytes 的值那就算螢幕上第 45 列第 125 行的點,它在記憶體中的位址是 A000:70FD 的 byte 的值相同可是卻顯示出不同的顏色。所以總共可以由 262114(2 的 18 次方)種 顏色中挑選 256 種顏色。R(紅色)、G(綠色)、B(藍色)每一種顏色由 1 個 byte 表示 但是 1 個 byte 中有兩個 bit 無效。這就是所謂的調色盤的觀念。下面就是一個圖例。 色彩暫存器

1byte 1byte 1byte 1btte 1byte 1byte 1byte 1byte 1byte 1byte 1byte 1byte 、 、 、 、 、 、 、 、 、 1byte 1byte 1byte 註:D/A 表示數位轉類比 R B G 圖 2-15 256 色模式成像顯示原理

2.2.3 DOS 遊戲的架構

由下圖我們可以看到一個飛機射擊遊戲可以由下面的 4 大部分組成。它們分別 是使用者控制輸入、遊戲初始化設定、遊戲運算和資料處理、螢幕輸出 顯 示 記 憶 體 8 位元 共 255 個

(28)

圖 2-16 DOS 遊戲架構圖 使用者控制輸入:它主要的功能是把使用者經由鍵盤按鍵的輸入把使用者目前是按 下哪一些鍵,右或者是放開哪一些鍵存到遊戲中的一個叫做 KEY 的變數中。 遊戲初始化設定:它主要的功能是設定畫面的解析度、設定有多少種顏色、從外部 讀入圖檔和背景檔、設定調色盤。 遊戲運算和資料處理:這裡主要的功能是依照變數 KEY 中的資料來控制主角的移 動…. 螢幕輸出:這裡只要要做的是就是依照遊戲運算和遊戲資料處理的所得結果來作輸 出。其中遊戲初始化設定又可以再細分為遊戲環境設定和檔案讀入而遊戲運算和 資料處理又可以在細分成主角控制、氣功控制,最後螢幕輸出也可以分成背景繪 製、主角的動畫製作、氣功的動畫製作。如下圖…. 圖 2-17 DOS 遊戲初始化設定架構圖 遊戲環境設定:它主要的功能是設定畫面的解析度、設定有多少種顏色、設定調色 盤。 檔案讀入:它主要的功能是由外部讀入圖檔存入顯示頁讀入背景檔。 DOS 遊戲 使用者控制 輸入 遊戲初始化設 定 遊戲運算和資 料處理 螢幕輸出 遊戲初始化設 定 遊戲環境設定 檔案讀入(讀入 bmp 和 mapgam)

(29)

圖 2-18 DOS 遊戲運算和資料處理架構圖 主角控制:它主要就是依照使用者的輸入來決定主角的移動或者是發射氣功。 氣功控制:它主要的功能就是讓氣功向前移動。 圖 2-19 螢幕輸出 接下來就是遊戲的流程圖。 遊戲運算和資 料處理 主角控制 氣功控制 螢幕輸出 主角的畫製 氣功的畫製 背景的畫製

(30)

是 否

2.2.3.1 遊戲初始化設定

遊戲初始化設定:它主要的功能是設定畫面的解析度、設定有多少種顏色、從 外部讀入圖檔和背景檔、設定調色盤。

2.2.3.1.1 遊戲環境設定

開始 遊戲環境設定 檔案讀入 使用者控制輸 入 是否按 下 ESC 主角控制 氣功控制 背景畫製 主角畫製 氣功畫製 1 1 結束

(31)

2.2.3.1.2 檔案讀取

檔案讀取主要是讀取程式中所有會使用到的圖檔資料。 開始 設定顯示模式 為為 256 色 設定顯示模式 為為 256 色 設定螢幕大小 為 640×480 設定鍵盤,讓 使用者可以用 鍵盤控制遊戲 結束

(32)

否 是 否 是 否 是 否 是 開始 開啟主角 圖檔成功 檔案讀取 指標指向 結尾 開啟地圖 圖檔成功 檔案讀取 指標指向 結尾 讀取主角 圖檔 讀取地圖圖檔 1

(33)

否 是 否 是 mapgam 開 啟成功 檔案讀取 指標指向 結尾 讀取 mapgam 結束 1

(34)

34

2.2.3.2 遊戲運算和資料處理

這裡主要的功能是依照變數 KEY 中的資料來控制主角的移動….

2.2.3.2.1 主角控制

這裡主要依照使用者的輸入來控制主角的移動或者是發射氣功。 輸入上 輸入下 輸入左 輸入右 輸入 P 否 否 否 否 是 是 是 是 開使 使用者輸 入 遇到 地圖 最頂 端 遇到 地圖 最下 端 遇到 地圖 最左 端 遇到 地圖 最右 端 向上移動 一個像點 向下移動 一個像點 向左移動 一個像點 向右移動 一個像點 停止移動 停止移動 停止移動 停止移動 1 2 3 4 產生氣功 設定氣功 起始位置 5

(35)

否 是 否 是 否 是 否 是 1 2 遇到 障礙 物體 停止移動 遇到 障礙 物體 停止移動 3 遇到 障礙 物體 停止移動 4 遇到 障礙 物體 停止移動 5 設定氣功 移動速度 結束

(36)

3.2.3.2.2 氣功控制

如果使用者有發射氣功,這裡控制氣功的移動。 向上 向下 向左 向右 否 是 否 是 是 否 是 否 開始 氣功移動的方 向 遇到 螢幕 最頂 端 遇到 螢幕 最下 端 遇到 螢幕 最左 端 遇到 螢幕 最右 端 向上移動 一個像點 向下移動 一個像點 向左移動 一個像點 向右移動 一個像點 氣功消失 氣功消失 氣功消失 氣功消失 1 2 3 4

(37)

否 是 否 是 否 是 否 是 1 2 遇到 障礙 物體 氣功消失 遇到 障礙 物體 氣功消失 3 遇到 障礙 物體 氣功消失 4 遇到 障礙 物體 氣功消失 結束

(38)

38

2.2.3.3 螢幕輸出

這個部分負責畫出背景、主角和主角發射的氣功

2.2.3.3.1 背景的繪製

否 是 否 是 開始 i=0 to 199 j=0 to 319 如果螢幕上第 i 列 第 j 行的像點不在 主角圖片範圍以內 如果螢幕上第 i 列 第 j 行的像點不在 氣功圖片範圍以內 算出這個像點的顏色 1 2 3 4

(39)

1 畫出這個像點 j 2 3 i 4 結束

(40)

2.2.3.3.2 主角的動畫製作

開始 i= 0 to 19 j= 0 to 19 把記憶體位置 p+j+i×20 的內容 複製到 s+j+x×20+320×i+y×20×320 就是說把主角圖片第 I 列第 j 行 的像點值拷貝到顯示記憶體要 輾是他的位置 p 是儲存主角圖片的記憶體位置 的起始指標,s 是顯示記憶體的 起始指標,(x,y)是主角在螢幕上 的位置。 j j 結束

(41)

41

2.2.3.3.3 氣功的動畫製作

開始 i= 0 to 19 j= 0 to 19 把記憶體位置 p+j+i×20 的內容 複製到 s+j+x×20+320×i+y×20×320 就是說把氣功圖片第 I 列第 j 行 的像點值拷貝到顯示記憶體要 輾是他的位置 p 是儲存氣功圖片的記憶體位置 的起始指標,s 是顯示記憶體的 起始指標,(x,y)是主角在螢幕上 的位置。 j j 結束

(42)

2.2.4 DOS GAME 程式碼

因為我的 DOS 模式底下的遊戲並不是成品。只是一個寫遊戲的試驗但是還是 從中間學到了粉多以下是完整的程式碼。 --- #include "alloc.h" #include "bios.h" #include "mem.h" #include <dos.h> #include <stdio.h>

#define BYTE unsigned char

//這是一個用來儲存中斷服務程式起始位置的指標 static void (interrupt far *old_kb_int)(void);

static int kb_init=0;

/********************************************************************/ /*這個函是把鍵盤中斷的起始位置設定成另外一個函式的起始位置 */ /* */ /*input 值是一個指向函式起始位置的指標 */ /*沒有回傳值 */ /********************************************************************/ void setkbint(void (interrupt far * new_kb_int)(void));

int key=0; /********************************************************************/ /*我們自己改寫的新的鍵盤中斷服務程式 */ /* */ /*沒有輸入值 */ /*沒有輸出值 */ /********************************************************************/ void interrupt far kb_int(void);

/********************************************************************/ /*把鍵盤中斷改回原來的鍵盤中斷 */ /********************************************************************/ void resetkbint(void); /********************************************************************/ /*清除鍵盤中斷狀態 */ /********************************************************************/ void clearkbintstate(void);

(43)

/********************************************************************/ /*這個函式是用來解碼 PCX 圖檔的 */ /* */ /*input char far aÎ圖檔名稱 */ /*input FILE *bÎ圖檔的檔案指標 */ /*input int cÎ圖片寬度 */ /*input int dÎ圖片長度 */ /*沒有輸出 */ /*******************************************************************/ void Decodepcxfile(char far *a,FILE *b,int c,int d);

/*******************************************************************/ /*解碼圖檔中的每一行 */ /* */ /*input FILE *bÎ圖檔的指標 */ /*input char far *aÎ圖檔的名稱 */ /*int cÎ圖檔的寬度 */ /*沒有輸出 */ /********************************************************************/ void Decodeline(char far *a,FILE *b,int c);

/********************************************************************/ /*設定調色盤為 256 色 */ /* */ /*沒有輸入 */ /*沒有輸出 */ /********************************************************************/ void Set256pal(); /********************************************************************/ /*設定顯示卡為繪圖模式 */ /* */ /*沒有輸入 */ /*沒有輸出 */ /********************************************************************/ void Initgraph(); /********************************************************************/ /*關閉顯示卡的繪圖模式改回文字模式 */ /* */ /*沒有輸入 */

(44)

/*沒有輸出 */ /********************************************************************/ void Closegraph(); /********************************************************************/ /*這個函式由 showmap()呼叫它負責顯示地圖 */ /********************************************************************/ void Show(); /********************************************************************/ /*這個函式把主角畫在(b*20,c*20)的位置 */ /* */ /*input char far *aÎ主角圖片所儲存的記憶體位置指標 */ /*input int bÎ主角的 x 座標除以 20 */ /*input int cÎ主角的 y 座標除以 20 */ /*沒有輸出值 */ /********************************************************************/ void Showman(char far *a,int b,int c);

/********************************************************************/ /*這個函式是負責畫出主角發射得武器 */ /* */ /*input char far *aÎ武器的圖片所存在的記憶體位置的指標 */ /*input int bÎ這個武器的 x 位置 */ /*input int cÎ這個武器的 y 位置 */ /*沒有輸出 */ /********************************************************************/ void Showpoken(char far *a,int b,int c);

/********************************************************************/ /*這個函式是個失敗品並沒有達到儲存螢幕的功能所以我在 MAIN 中從未乎叫*/ /*這個函式 */ /********************************************************************/ void savescreenmap(char far *,int,int);

/********************************************************************/ /*這個函式畫出背景地圖 */ /********************************************************************/ void Showmap(); /********************************************************************/ /*一個定義敵人的結構 */ /********************************************************************/

(45)

struct ghost { int ox,oy,nx,ny; int direct; int exist; }; /********************************************************************/ /*一個定義主角所發射出的武器的結構 */ /********************************************************************/ struct POKEN { int poken_x,poken_y; int poken_x_in_screen,poken_y_in_screen;

int poken_direction;//0 for up,1 for down,2 for left,3 for right; int poken_state;// 0 for not exist,1 for exist;

char far *poken_pcx; };

struct ghost kk[10]; struct POKEN poken[10]; char palet[768];

char palet0[768];

char far *pta,*ptb,*pp,*pg,*wholescreen; int mp; unsigned actor_x,actor_y; char map[80][80]; void main(void) { FILE *file_ptr; int c,i=0; int up=0,down=0,left=0,right=0; unsigned bufseg,bufoff; //file_ptr=fopen("thing.pcx","rb");

//pt=(char far *)malloc(20*20*5); /* allocate memory buffer for .pcx */ //fseek(file_ptr,-768,SEEK_END);/* find the palette */

//fread(palet,1,768,file_ptr) ;/* read the palette to palet[] */ //for(c=0;c<768;++c)/* transfer palet[] to match .pcx file */ //{

(46)

// palet[c]>>=2; //}

//fseek(file_ptr,128,SEEK_SET); /*move pointer to data part*/

//Decodepcxfile(pt,file_ptr,5*20,20); /* decode a .pcx file to memory buffer */ //

file_ptr=fopen("ch4a.pcx","rb"); pta=(char far *)malloc(20*20*32); fseek(file_ptr,-768,SEEK_END); fread(palet,1,768,file_ptr); for(c=0;c<768;++c) { palet[c]>>=2; } fseek(file_ptr,128,SEEK_SET); Decodepcxfile(pta,file_ptr,2*20,320); // file_ptr=fopen("ch4b.pcx","rb"); ptb=(char far *)malloc(20*20*16); fseek(file_ptr,-768,SEEK_END); fread(palet,1,768,file_ptr); for(c=0;c<768;++c) { palet[c]>>=2; } fseek(file_ptr,128,SEEK_SET); Decodepcxfile(ptb,file_ptr,1*20,320); // file_ptr=fopen("people.pcx","rb"); pp=(char far *)malloc(20*20*8); fseek(file_ptr,-768,SEEK_END); fread(palet,1,768,file_ptr); for(c=0;c<768;++c) { palet[c]>>=2; } fseek(file_ptr,128,SEEK_SET); Decodepcxfile(pp,file_ptr,8*20,20);

(47)

fclose(file_ptr);

file_ptr=fopen("badman","rb");

pg=(char far *)malloc(20*20); /* allocate memory buffer for .pcx */ fread(pg,1,400,file_ptr) ; /* read the palette to palet[] */

fclose(file_ptr);

wholescreen=(char far *)malloc(64000); if(wholescreen=='\0') { printf("ppppppppp\n"); //getchar(); } file_ptr=fopen("map.gam","rb"); for(i=0;i<=79;i++) { for(c=0;c<=79;c++) { map[c][i]=fgetc(file_ptr); } }

Initgraph(); /*get into graph mode(320x200) */ Set256pal();/* set the paletet */

printf("PRESS ENTER TO GO ON ..."); for(i=0;i<=19;i=i+1) { kk[i].exist=0; } kk[0].exist=1; kk[0].ox=3; kk[0].oy=6; // actor_x=380; actor_y=120; setkbint(kb_int); while(key!=0x01) { if(key!=0x01) {

(48)

switch(key) { case 0x4d: { actor_x++; if((map[(actor_x+19)/20][(actor_y)/20]&0x80==0x80)||(map[(actor_x+19)/20][(actor_y +19)/20]&0x80==0x80)) { actor_x=actor_x-1; } if((right++)%2==0) { mp=4; } else { mp=5; } break; } case 0x4b: { actor_x--; if((map[(actor_x)/20][(actor_y)/20]&0x80==0x80)||(map[(actor_x/20)][(actor_y+19)/20 ]&0x80==0x80)) { actor_x=actor_x+1; } if((left++)%2==0) { mp=6; } else { mp=7;

(49)

} break; } case 0x48: { actor_y--; if((map[(actor_x)/20][(actor_y)/20]&0x80==0x80)||(map[(actor_x+19)/20][(actor_y)/20 ]&0x80==0x80)) { actor_y=actor_y+1; } if((up++)%2==0) { mp=2; } else { mp=3; } break; } case 0x50: { actor_y++; if((map[(actor_x)/20][(actor_y+19)/20]&0x80==0x80)||(map[(actor_x+19)/20][(actor_y +19)/20]&0x80==0x80)) { actor_y=actor_y-1; } if((down++)%2==0) { mp=0; } else {

(50)

mp=1; }

break; }

case 0x18://0x18 is p's scane code; { i=0; if(mp==0||mp==1) { poken[i].poken_pcx=pg; poken[i].poken_state=1; poken[i].poken_x=actor_x; poken[i].poken_y=actor_y+20; poken[i].poken_x_in_screen=140; poken[i].poken_y_in_screen=100+20; poken[i].poken_direction=1; } if(mp==2||mp==3) { poken[i].poken_pcx=pg; poken[i].poken_state=1; poken[i].poken_x=actor_x; poken[i].poken_y=actor_y-20; poken[i].poken_x_in_screen=140; poken[i].poken_y_in_screen=100-20; poken[i].poken_direction=0; } if(mp==4||mp==5) { poken[i].poken_pcx=pg; poken[i].poken_state=1; poken[i].poken_x=actor_x+20; poken[i].poken_y=actor_y; poken[i].poken_x_in_screen=140+20; poken[i].poken_y_in_screen=100; poken[i].poken_direction=3; }

(51)

if(mp==6||mp==7) { poken[i].poken_pcx=pg; poken[i].poken_state=1; poken[i].poken_x=actor_x-20; poken[i].poken_y=actor_y; poken[i].poken_x_in_screen=140-20; poken[i].poken_y_in_screen=100; poken[i].poken_direction=2; }

//printf("i love you"); break; } default: { break; } } } Showmap(); Showman(pp+mp*400,7,5);

for(i=0;i<=0;i++)//decide the position of poken; { if(poken[i].poken_state==1) { switch(poken[i].poken_direction) { case 0: { poken[i].poken_x=poken[i].poken_x; poken[i].poken_y=poken[i].poken_y-1; poken[i].poken_x_in_screen=poken[i].poken_x_in_screen; poken[i].poken_y_in_screen=poken[i].poken_y_in_screen-1; break; } case 1: {

(52)

poken[i].poken_x=poken[i].poken_x; poken[i].poken_y=poken[i].poken_y+1; poken[i].poken_x_in_screen=poken[i].poken_x_in_screen; poken[i].poken_y_in_screen=poken[i].poken_y_in_screen+1; break; } case 2: { poken[i].poken_x=poken[i].poken_x-1; poken[i].poken_y=poken[i].poken_y; poken[i].poken_x_in_screen=poken[i].poken_x_in_screen-1; poken[i].poken_y_in_screen=poken[i].poken_y_in_screen; break; } case 3: { poken[i].poken_x=poken[i].poken_x+1; poken[i].poken_y=poken[i].poken_y; poken[i].poken_x_in_screen=poken[i].poken_x_in_screen+1; poken[i].poken_y_in_screen=poken[i].poken_y_in_screen; break; } default: { break; } } }

//printf("actor position (%d,%d)",actor_x,actor_y); } for(i=0;i<=0;i=i+1) { if(poken[i].poken_state==1) { Showpoken(pp+mp*400,poken[0].poken_x_in_screen,poken[0].poken_y_in_scree n);

(53)

} } } resetkbint(); Closegraph(); farfree(pta); farfree(ptb); farfree(pp); farfree(pg); farfree(wholescreen); } /********************************************************************/ /*這個函是把鍵盤中斷的起始位置設定成另外一個函式的起始位置 */ /* */ /*input 值是一個指向函式起始位置的指標 */ /*沒有回傳值 */ /********************************************************************/ void setkbint(void (interrupt far *new_kb_int)(void))

{ disable(); if (kb_init==0) { old_kb_int=getvect(0x09); kb_init=1; } setvect(0x09,new_kb_int); enable(); } /********************************************************************/ void interrupt far kb_int(void);

/********************************************************************/ void interrupt far kb_int(void)

{

key=inportb(0x60)&0x7f; clearkbintstate();

}

(54)

/*把鍵盤中斷改回原來的鍵盤中斷 */ /********************************************************************/ void resetkbint(void) { if (kb_init==1) { disable(); setvect(0x09,old_kb_int); kb_init=0; enable(); } } /********************************************************************/ /*清除鍵盤中斷狀態 */ /********************************************************************/ void clearkbintstate(void) { BYTE code; code=inportb(0x61); code|=0x80; outportb(0x61,code); code&=0x7f; outportb(0x61,code); outportb(0x20,0x20); enable(); }/********************************************************************/ /*這個函式由 showmap()呼叫它負責顯示地圖 */ /********************************************************************/ void Show(void) { int i=0,j=0,k=0,column=0,row=0; int flage=0; char m;

char far *temp_ptr; unsigned x,y,a,b; unsigned bufseg,bufoff; a=actor_x-140;

(55)

b=actor_y-100; x=actor_x%20; y=actor_y%20; for(i=0;i<=199;i++) { for(j=0;j<=319;j++) { if(i>=100&&i<120&&j>=140&&j<160) { a=a+1; x=x+1; if(x>=20) { x=0; } } else { flage=0; for(k=0;k<=0;k++) { if(poken[k].poken_state==1) { if((i>=poken[k].poken_y_in_screen&&i<=poken[k].poken_y_in_screen+19)&&(j>=po ken[k].poken_x_in_screen&&j<poken[k].poken_x_in_screen+19)) { a=a+1; x=x+1; if(x>=20) { x=0; } flage=1; }

//printf("i love you"); }

(56)

} if(flage==0) { m=map[a/20][b/20]; if((m&0x80)==0x80) { temp_ptr=pta; } else { temp_ptr=ptb; } column=(int)(( m & 0x70)>>4); row=(int)( m & 0x0f); bufseg=FP_SEG(temp_ptr+320*20*column+320*y+20*row+x); bufoff=FP_OFF(temp_ptr+320*20*column+320*y+20*row+x); movedata(bufseg,bufoff,0x0a000,j+i*320,1); a=a+1; x=x+1; if(x>=20) { x=0; } } } } a=actor_x-140; b=b+1; x=actor_x%20; y=y+1; if(y>=20) { y=0; } } b=actor_y-100; }

(57)

/********************************************************************/ void Showman(char far *a,int b,int c);

/********************************************************************/ void Showman(char far *p,int x,int y)

{ int i,j; unsigned bufseg,bufoff; for(i=0;i<20;i++) { for(j=0;j<20;j++) { bufseg=FP_SEG(p+j+i*20); bufoff=FP_OFF(p+j+i*20); movedata(bufseg,bufoff,0x0a000,j+x*20+320*i+y*20*320,1); } } } /********************************************************************/ void Showpoken(char far *a,int b,int c);

/********************************************************************/ void Showpoken(char far *p,int x,int y)

{ int i,j; unsigned bufseg,bufoff; for(i=0;i<20;i++) { for(j=0;j<20;j++) { bufseg=FP_SEG(p+j+i*20); bufoff=FP_OFF(p+j+i*20); movedata(bufseg,bufoff,0x0a000,j+x+320*i+y*320,1); } } } /********************************************************************/ /*這個函式是個失敗品並沒有達到儲存螢幕的功能所以我在 MAIN 中從未乎叫*/ /*這個函式 */

(58)

/********************************************************************/ void savescreenmap(char far *p,int x,int y)// now is no use;

{ int i; unsigned bufseg,bufoff; for(i=0;i<20;i++) { bufseg=FP_SEG(p+i*20); bufoff=FP_OFF(p+i*20) ; movedata(bufseg,bufoff,FP_SEG(wholescreen),FP_OFF(wholescreen)+x*20+320*i+y* 20*320,20); } } /********************************************************************/ /*這個函式畫出背景地圖 */ /********************************************************************/ void Showmap(void) { int x,y,m; unsigned bufseg,bufoff; Show(); /*for(loop=0;loop<=19;loop=loop+1) { //decisionghostpos(loop); //moveghost(loop); //checktouch(loop); if(kk[loop].exist==1) { if((kk[loop].ox!=(7+xi))||(kk[loop].oy!=(5+yi))) { if(kk[loop].ox<(7+xi)) { kk[loop].ox=kk[loop].ox+1; } if(kk[loop].ox>(7+xi)) { kk[loop].ox=kk[loop].ox-1;

(59)

} } if((kk[loop].ox<=(9+xi))&&(kk[loop].ox>=(0+xi))&&(kk[loop].oy<=(15+yi))&&(kk[l oop].oy>=(0+yi))) { Show(pg,kk[loop].ox-xi,kk[loop].oy-yi); } } }*/ } /********************************************************************/ /*設定顯示卡為繪圖模式 */ /* */ /*沒有輸入 */ /*沒有輸出 */ /********************************************************************/ void Initgraph() /* into 320x200 mode */

{ _AX=0x0013; geninterrupt(0x10); } /********************************************************************/ /*關閉顯示卡的繪圖模式改回文字模式 */ /* */ /*沒有輸入 */ /*沒有輸出 */ /********************************************************************/ void Closegraph() { _AX=0x0003; geninterrupt(0x10); } /********************************************************************/ /*設定調色盤為 256 色 */ /* */

(60)

/*沒有輸入 */ /*沒有輸出 */ /********************************************************************/ void Set256pal()

{

extern char palet[768]; _ES=FP_SEG(palet); _DX=FP_OFF(palet); _CX=256; _BX=0x0000; _AX=0x1012; geninterrupt(0x10); } /*******************************************************************/ /*解碼圖檔中的每一行 */ /* */ /*input FILE *bÎ圖檔的指標 */ /*input char far *aÎ圖檔的名稱 */ /*int cÎ圖檔的寬度 */ /*沒有輸出 */ /********************************************************************/ void Decodeline(char far *ptr,FILE *file_ptr,int length)

{

int data,count; int len=0;

while(len<length) /* loop for a line */ {

data=fgetc(file_ptr); if(data <= 0xc0)

{ /* if single data */

*(ptr+(len++))=data; /* put single data */ }

else /* else */ { /* get repeat counts */ count=data&0x3f;

data=fgetc(file_ptr); /* get data */ while(count--) /* put repeat data */

(61)

{ *(ptr+(len++))=data; } } } }/********************************************************************/ /*這個函式是用來解碼 PCX 圖檔的 */ /* */ /*input char far aÎ圖檔名稱 */ /*input FILE *bÎ圖檔的檔案指標 */ /*input int cÎ圖片寬度 */ /*input int dÎ圖片長度 */ /*沒有輸出 */ /*******************************************************************/ void Decodepcxfile(char far *ptr,FILE *file_ptr,int length,int width)

{

int i;

for(i=0;i<length;i++) /* decode whole file to the memory buffer */ {

Decodeline(ptr+i*width,file_ptr,width); }

}

數據

圖 2-1    INTERRUPT 圖例          中斷的來源有兩種,一種來自於硬體一種來自於軟體。硬體的中斷可能來自 於 CPU 內部或著是外部的中斷設備,而軟體的中斷則如同一般的指令一樣,以指 令的形式存在於一般的程式片斷當中。          當 CPU 執行到某一種特殊狀況時,會自動執行中斷的動作。這種中斷叫做 CPU 內部的中斷,例如當 CPU 發生除以 0 的運算時,便會執行第 0 號中斷。此外第 1 號和第 4 號中斷也是 CPU 內部的中斷。當初 Intel 公司是保留 INT

參考文獻

相關文件

„ „ 利用電腦來安排與整合多種媒體,可產生 利用電腦來 更多樣化的作品。如某一段背景配樂在影 片中的哪個時間點開始播放、新聞播報中 子母畫面的相對位置、文字字幕出現在畫

師一趟,後來他也想把我帶去台南,一開始我還覺得是不是又是哪個江湖術士在 騙人,但我哥說在 程老師 程老師

一開始,老師先教我們認識 器材,後來就讓我們自己拍 照。我覺得拍蚜蟲的照片很 好玩,因為蚜蟲本來只有一

這次的實驗課也分成兩個禮拜完成,在實驗過程中我們幾乎都很順利完成了課堂上要達到的目標

Ans:如果不稀釋的話,一剛開始的電導率很大,需要滴入大量的溶液,浪費

使我們初步掌握了電壓、電流和電阻三者之間的關係。我

她寫道,當我們在生活中最想做的事情也是我們的義務時,最能 感受到 Ikigai 。關於 Ikigai ,感受就是最誠實的,如果我們知道如何

我一開始對這門課的目標只是想單純上課認真抄筆記、作業好好 寫、絕不早退外,還從未想過會上台報告。雖然我是老師點到要