? RPG制作之路
???????????????????????????????????? 天眼

一、緒論
??? RPG是角色扮演游戲的英文縮寫(xiě),它是紙上冒險(xiǎn)游戲和電腦結(jié)合的產(chǎn)物,自從誕生起就以獨(dú)特的
冒險(xiǎn)和故事性吸引了無(wú)數(shù)的玩家。我個(gè)人認(rèn)為,RPG游戲是各類游戲中最能表述故事,體現(xiàn)情感的一
種游戲。RPG游戲不但自身在不斷發(fā)展著,而且還不斷吸取著其他游戲的精華,如加入冒險(xiǎn)類的解密
情節(jié)(大多數(shù)RPG游戲都有),加入動(dòng)作游戲的戰(zhàn)斗方式(暗黑破壞神),引入戰(zhàn)棋類游戲的策略戰(zhàn)
斗部分(金庸群俠傳),隨著3D技術(shù)的發(fā)展,優(yōu)秀的三維RPG游戲也在不斷的涌現(xiàn)(如魔法門六)。
??? 然而這些優(yōu)秀的游戲并不是憑空產(chǎn)生的,它同其他軟件類產(chǎn)品一樣,它也是由許多的程序人員編
制出來(lái)的,而且它的產(chǎn)生還離不開(kāi)策劃人員、美工和音樂(lè)制作人員協(xié)同的努力工作。RPG游戲的靈魂
是劇情,然而它卻和其他類型游戲不同,它的特別強(qiáng)的故事性使得游戲的美工、音樂(lè)幾乎同樣重要。
然而卻也不能因?yàn)橹匾曀鼈兌鲆暳藙∏椋驗(yàn)檫@三者互相失衡而導(dǎo)致失敗的游戲的確不在少數(shù)。
??? 我非常喜歡編程序,看著計(jì)算機(jī)在自己的程序控制下說(shuō)一是一,說(shuō)二是二,讓它向南它不敢往北
,哪怕是完成了一個(gè)非常小的程序,自己也會(huì)有一種成功的喜悅。當(dāng)完成一個(gè)久未解決的難題時(shí),真
想跳起來(lái)振臂高呼(我也真的這么干過(guò),被人罵作"神經(jīng)")。相信真正沉浸其中的人都會(huì)有此感覺(jué)

??? 自從我剛剛接觸電腦起,我就同樣接觸了游戲,雖然那只是在APPLE-II上的非常簡(jiǎn)單的游戲,我
就幾乎沉迷了。沉迷的當(dāng)然不是玩這些游戲,而是自己編這些游戲。那時(shí)我真的很驚訝,原來(lái)可以通
過(guò)自己的努力,讓電腦給其他人提供這么吸引人的娛樂(lè)服務(wù)(那時(shí)我一直以為電腦是來(lái)進(jìn)行科學(xué)計(jì)算
的)。雖然我那時(shí)在蘋(píng)果機(jī)上編制的游戲簡(jiǎn)直沒(méi)有娛樂(lè)性,但我還是很看重那一段時(shí)光,因?yàn)槲以谀?br />時(shí)不但認(rèn)識(shí)到了電腦的超凡的功能和極端廣泛的應(yīng)用范圍,而且還為我打下了程序設(shè)計(jì)的基礎(chǔ)。
??? 這些年來(lái),我自己編的游戲大大小小也有十來(lái)個(gè),雖然真正的成品很少,但我還是努力嘗試了游
戲的各種類型,包括射擊、經(jīng)營(yíng)模擬、即時(shí)策略、戰(zhàn)棋,當(dāng)然最多的還是RPG游戲。??? 下面我就結(jié)
合自己的經(jīng)驗(yàn),講解一下RPG游戲制作的具體方法和步驟,獻(xiàn)給那些有程序設(shè)計(jì)經(jīng)驗(yàn)且想自己制作游
戲的玩家。所有這些都是我個(gè)人的實(shí)踐和思考的結(jié)果,可能會(huì)有許多不對(duì)的地方,歡迎大家批評(píng)指正

二、策劃部分
1.? 策劃的必要性
??? 有句老話叫"未雨而綢繆",是說(shuō)應(yīng)當(dāng)為即將發(fā)生的事做好準(zhǔn)備,在這里可以理解為即將開(kāi)始的
游戲制作做好準(zhǔn)備工作。因?yàn)榧词故且粋€(gè)很小的游戲或者是軟件,也有很多獨(dú)立的方面需要完成。如
標(biāo)題畫(huà)面,操作界面,和系統(tǒng)有關(guān)的設(shè)置甚至還有安裝等很多方面。如果事先不經(jīng)過(guò)計(jì)劃,在制作的
過(guò)程中必然會(huì)不斷發(fā)現(xiàn)沒(méi)有考慮到或者考慮不周全的地方,如果這時(shí)在進(jìn)行改動(dòng),你的程序、美工和
音樂(lè)大概都得跟著改,有時(shí)甚至?xí)?dǎo)致整個(gè)工作重來(lái)。這樣先前的許多工作都變成了白白的時(shí)間浪費(fèi)
。我以前也走過(guò)這樣一段彎路,總是一開(kāi)始就坐在計(jì)算機(jī)前開(kāi)始編程序,并且不斷發(fā)現(xiàn)某些方面沒(méi)法
實(shí)現(xiàn),不得不修改以前的代碼,有時(shí)還因?yàn)橐粋€(gè)重要的方法技術(shù)上沒(méi)有辦法實(shí)現(xiàn),被迫放棄制作。過(guò)
了一段時(shí)間后又突然解決了這個(gè)問(wèn)題,再想拾起以前的工作繼續(xù)下去,幾乎已經(jīng)不可能了。經(jīng)過(guò)幾番
掙扎,我終于認(rèn)識(shí)到了策劃的重要性,現(xiàn)在,無(wú)論是做什么東西,我總是"三思而后行",有時(shí)我還
會(huì)提前編一些小程序驗(yàn)證某些方法的可行性,不會(huì)再盲目開(kāi)始了。
??? 相信大部分有的程序設(shè)計(jì)經(jīng)驗(yàn)的玩家都會(huì)同意我的看法,我之所以說(shuō)這些話也是為了讓大家少走
些彎路,更快的掌握制作游戲的方法。
2. 劇情的策劃
??? 很多游戲制作者將詳細(xì)的劇情策劃放在第一步,我對(duì)此有不同的看法。
??? 劇情的策劃固然重要,因?yàn)橐巳雱俚膭∏橥荝PG游戲制作的關(guān)鍵,然而理想化的劇情策劃
經(jīng)常為程序設(shè)計(jì)制造難題,程序雖然可以不斷完善,但出于技術(shù)和面向玩家階層的考慮,程序并不是
萬(wàn)能的,策劃者卻往往不清楚程序描述劇情的能力達(dá)到到什么程度,當(dāng)程序能力有所不及,就得重新
修改策劃了!這是所有人都不愿看到的。
  將劇情策劃放在程序設(shè)計(jì)之后更是不可能的,因?yàn)槌绦蛟O(shè)計(jì)者將無(wú)所指導(dǎo),他不知道自己的程序
需要達(dá)到什么程度!
  想到這里,我們不僅想到了在C語(yǔ)言程序中兩個(gè)互相調(diào)用函數(shù)的情況,誰(shuí)先誰(shuí)后呢?那時(shí),解決
的方法是函數(shù)原形,將相對(duì)基礎(chǔ)的函數(shù)的函數(shù)名、輸入輸出參數(shù)以函數(shù)原形的方式寫(xiě)在所有調(diào)用它的
函數(shù)之前。同樣我們可以將相對(duì)基礎(chǔ)的"劇情"的大體框架和對(duì)程序的要求放在工作的第一步,在程
序設(shè)計(jì)完成以后在填充它的具體細(xì)節(jié)。在程序的基礎(chǔ)上完成具體的劇情細(xì)節(jié),這樣就能成分發(fā)揮程序
的表述能力了!
3. 劇情框架
??? 應(yīng)當(dāng)主要描述劇情的時(shí)代背景,環(huán)境氛圍,畫(huà)面風(fēng)格,事件的表述方法。至于具體的故事細(xì)節(jié),
等到程序完成以后在進(jìn)行設(shè)計(jì)也未嘗不可。
??? 對(duì)于改編電影、小說(shuō)的游戲,劇情已定,首先所需要的就是決定是否需要照搬原著的劇情?很重
要的一點(diǎn)是電影、小說(shuō)有自己獨(dú)到的表現(xiàn)方法,有些對(duì)于游戲是值得借用的,但并不表示要完全借用
。如果完全借用,那和電影、小說(shuō)本身還有什么區(qū)別呢,我相信大多數(shù)人是不喜歡VCD般的游戲和電
子小說(shuō)般的游戲的。
4. 決定程序設(shè)計(jì)的基礎(chǔ)
???? (1) 這一部分主要是指游戲的圖形模式(分辨率、顏色深度),以及采用的特殊效果、音樂(lè)、
音效和動(dòng)畫(huà)的支持。由于本人對(duì)3D編程涉獵不多,所以主要以2D為主。
???? 圖形模式由顯示卡決定,由顯示器支持,一般游戲常用的顯示模式有
  320 X 200 X 8 bits
??? 640 X 480 X 8 bits
??? 800 X 600 X 8 bits
?? 1024 X 768 X 8 bits
??? 640 X 480 X 16 bits
??? 800 X 480 X 16 bits
?? 1024 X 768 X 16 bits? (需顯存2M)
??? 640 X 480 X 24 bits
??? 800 X 600 X 24 bits? (需顯存2M)
?? 1024 X 768 X 24 bits? (需顯存4M)
??????? 未注明的顯存1M即可?
??? 現(xiàn)在的顯示器大都支持640X480、800X600、1024X768這三種分辨率,而顯示卡對(duì)顏色深度的支持
與顯存有關(guān):
?????????????????????????????????????????? 顏色深度(bit)
?? 所須顯存數(shù)量=橫坐標(biāo)分辨率X縱坐標(biāo)分辨率X-------------
??????????????????????????????????????????????? 8
??????? 顏色深度(bit)
????? 顏色數(shù) = 2????
????????????????????????????? 8
??????? 如顏色深度8 bit即? 2??? =? 256 色
???? 有些顯示卡支持32bit或者更高的顏色深度,但24bit的顏色深度就已經(jīng)使人眼分辨顏色的能力
達(dá)到極限,這些更高的顏色深度主要用于高質(zhì)量的圖象和視頻設(shè)計(jì)。
?????? 注:256色的調(diào)色機(jī)理和16bit、24bit的不同,現(xiàn)在的編程語(yǔ)言大都已經(jīng)封裝了對(duì)顯示卡的控
制,制作者不用了解其中的原理就可以很方便的使用。
??? 分辨率越大、顏色數(shù)更多,圖形的表現(xiàn)自然更好,但對(duì)顯存、內(nèi)存和數(shù)據(jù)傳輸?shù)男枨缶驮礁撸瑘D
形數(shù)據(jù)文件也就越大。考慮到大多數(shù)電腦玩家的顯卡都至少有1M顯存,因此640x480x8bit、
800x600x8bit、640x480x16bit、800x600x16bit、640x480x24bit成了大多數(shù)游戲通用的分辨率,我
在制作游戲時(shí)也主要考慮這幾種分辨率。需要較快的屏幕顯示速度可以采用256色,需要光影等特殊
效果的最好采用16bit或24bit的顏色數(shù),這些需要依具體情況定。至于分辨率,可以單獨(dú)使用一種,
也可以同時(shí)支持幾種,因?yàn)閷?duì)于各種編程軟件來(lái)說(shuō),這似乎并不是什么難題!
(2) 圖塊類型的選擇
??? 大部分的RPG游戲的圖形都是由數(shù)層圖案逐層覆蓋而成,比如最底的一層為地表圖案如草地、石
子路等等;次一層為地物,包括其中行走的主角和NPC的圖案;再高一層可以為覆蓋于前一層的物體
之上的東西,如屋頂、樹(shù)梢等等;如果需要的話,還可以再在上面加入表示天氣一層,如白云、雨水
什么的。當(dāng)這些圖層重疊起來(lái)以后,上面一層鏤空的地方露出下面的一層,這樣就形成了"人在地上
走,云在人上飄"的層次高度效果。當(dāng)然你也可以制定自己的圖層,實(shí)現(xiàn)所需的特殊效果。
??? 游戲的每一個(gè)圖層可以由多種方法實(shí)現(xiàn):
???? ——單張圖片構(gòu)成
???????? 非常容易發(fā)揮美術(shù)效果,適合做地表,天氣效果,但所需的內(nèi)存和硬盤空間著實(shí)不小。
???? ——多張不規(guī)則圖片構(gòu)成
???????? 美術(shù)效果不錯(cuò),適合做為地物層,但當(dāng)需要構(gòu)成遮擋效果時(shí),計(jì)算較為麻煩 。
???? ——多張規(guī)則圖片組成
???????? 最節(jié)省內(nèi)存和硬盤空間,適合做任何層次,遮擋也非常容易處理,但對(duì)美工的要求比較高

???? 大多數(shù)的RPG游戲還是采用最后一種,因?yàn)榧夹g(shù)上最為簡(jiǎn)單(用二維數(shù)組就可以描述),所以我
也比較傾向于這種,主要講講它。
???? 規(guī)則圖案組成層次也有幾種:
????? ——矩形(出于對(duì)各個(gè)方向卷軸速度一致的考慮,多為正方型)圖片拼接而成,本層內(nèi)的圖片
不存在互相遮擋的關(guān)系,只需從左到右,從上到下依次畫(huà)出即可。只能創(chuàng)建出頂視或非常假的無(wú)遮擋
3D效果。(如《俠客英雄傳》、早期的《勇者斗惡龍》系列)
????? ——等邊六角型圖片拼接,就象蜂巢,本層內(nèi)圖片不存在遮擋關(guān)系。這種方式多用在戰(zhàn)棋類游
戲中,或RPG游戲的戰(zhàn)斗部分,在行走地圖中不常用(因?yàn)榈糜辛鶄€(gè)行走方向,象是《英雄無(wú)敵》系
列)。
????? ——矩形圖片逐行覆蓋而成,這種游戲的圖片按著從左到右,從上到下依次畫(huà)出,但每一行的
圖片都覆蓋前一行少許,但一個(gè)圖片只覆蓋本列的圖片。這樣的話,就可以理解為近處的物體遮擋了
遠(yuǎn)處東西,就是屏幕上部離玩家最遠(yuǎn),下部離玩家最最近。產(chǎn)生一定的立體效果。(如《俠客英雄傳3》、《新蜀山劍俠》和《劍俠情緣》)
????? ——矩形圖片交錯(cuò)覆蓋,每一行不但覆蓋前一行,而且橫向偏移一段距離。這樣就產(chǎn)生了視角
傾斜的效果,正方型的底面在這種視角中呈菱形。這種效果比上一種方法能產(chǎn)生更好的立體感覺(jué),美
工好的話可以以假亂真。(如《仙劍奇?zhèn)b傳》、《金庸群俠傳》和《暗黑破壞神》等)
???? 在這一部分很難用言語(yǔ)描述,我只講各種類型,以后制作HTML版,再加入示意圖片。大家只要
先有一個(gè)概念就行,
至于具體的數(shù)據(jù)描述和繪制方法我會(huì)在以后的程序部分講解。
(3)? 構(gòu)成游戲的基本要素。
???? 單線還是多線劇情?
???? 單線劇情的和多線劇情對(duì)于程序員的工作來(lái)說(shuō),差別不大。但對(duì)于策劃人員來(lái)說(shuō),卻決不相同
。多線劇情相對(duì)于單線擁有數(shù)倍的工作量,而且大量的分支很容易搞亂。但多線劇情的可玩性十分誘
人,越來(lái)越多的制作者開(kāi)始采用它。如果你決定使用這種方式,一定要做好策劃,管好所有的資料數(shù)
據(jù)。(我的建議是專門編制一個(gè)管理多分支劇情的(信息、文本)編輯器,這并不時(shí)小題大做,因?yàn)?br />是你完全可以在編制下一個(gè)游戲時(shí)再次使用它。如果要求不太高的情況下,使用HTML語(yǔ)言是一種節(jié)省
效率的方法。)
???? 哪些人物屬性?
???? 人物屬性在RPG游戲中往往可以決定戰(zhàn)斗、升級(jí)和劇情,是玩家非常在意的東西,因此對(duì)人物屬
性的設(shè)定也要多化些工夫。擁有較多的屬性可以產(chǎn)生更多的趣味性,你不必?fù)?dān)心占用內(nèi)存的問(wèn)題,因
為這跟圖象數(shù)據(jù)的內(nèi)存需求比起來(lái)簡(jiǎn)直微不足道。但切記不要強(qiáng)硬的添加無(wú)用或是作用不大的屬性。
???? 什么樣的戰(zhàn)斗方式?
???? 戰(zhàn)斗是RPG游戲中的重頭戲,目前RPG游戲的戰(zhàn)斗方式有以下幾種:
??? ——敵我交替攻擊的回合制:主要為早期的RPG游戲所采用,代表如《仙劍奇?zhèn)b傳》
??? ——敵我同時(shí)攻擊的回合制:為《劍俠情緣》中對(duì)前一種戰(zhàn)斗方法的改進(jìn),意義不大。
??? ——戰(zhàn)棋式的回合制:最有代表的是《金庸群俠傳》
??? ——即時(shí)戰(zhàn)斗:就在行走地圖上展開(kāi)的戰(zhàn)斗,如《圣光島》和《暗黑破壞神》
??? ——格斗游戲方式:這是國(guó)內(nèi)的晶合公司的在其游戲《毀滅天地》中的獨(dú)創(chuàng)。(其實(shí)我也這么想
過(guò),就是沒(méi)有做過(guò)!)
??? 前兩種戰(zhàn)斗的實(shí)現(xiàn)方式較為簡(jiǎn)單,后三種則相當(dāng)于融入了另外一種游戲的概念,所以制作起來(lái)比
較困難,不過(guò)也很有挑戰(zhàn)性,很可能會(huì)吸引更多的玩家。
??? 什么樣的升級(jí)系統(tǒng)?
??? 升級(jí)系統(tǒng)模仿了人類的成長(zhǎng),隨著能力的提高去面對(duì)更加強(qiáng)大的敵人,也是RPG游戲所特有的魅
力,但近來(lái)也被不少其他類型的游戲(如即時(shí)策略和動(dòng)作類)所吸收。作為傳統(tǒng)的RPG當(dāng)然更不能少
。目前各類RPG游戲的升級(jí)系統(tǒng)很單一,也是目前者最有有發(fā)揮潛力地方。在這里,我講一些自己的
設(shè)想和看法。
?????? ——通過(guò)戰(zhàn)斗獲取經(jīng)驗(yàn)值來(lái)升級(jí),由等級(jí)數(shù)、資質(zhì)之類的人物的屬性來(lái)決定升級(jí)所需的經(jīng)驗(yàn)
值,等級(jí)的提升又提高人物的某些屬性,提高人物的戰(zhàn)斗力。這也是最基本的升級(jí)規(guī)律,只需要制定
一些公式就可以實(shí)現(xiàn),當(dāng)然記著還要包含一定隨機(jī)性(使用偽隨機(jī)函數(shù))。
?????? ——吸取MUD的優(yōu)點(diǎn),設(shè)置一些如"根骨"、"敏捷"、"悟性"等多項(xiàng)資質(zhì)屬性,對(duì)角色各
項(xiàng)屬性的成長(zhǎng)影響各自不同,通過(guò)劇情可以修改這些基本參數(shù),創(chuàng)建出多種不同的角色成長(zhǎng)方式。
?????? ——對(duì)于偏重于戰(zhàn)斗的RPG游戲,設(shè)置較多"全局"角色,即在整個(gè)游戲過(guò)程都存在,他們擁
有同主角一致的升級(jí)系統(tǒng),采用半隨機(jī)(由人物參數(shù)、隨機(jī)值和劇情共同決定)獲取經(jīng)驗(yàn)值。這樣就
可以產(chǎn)生即時(shí)性的人物成長(zhǎng),當(dāng)玩家游手好閑時(shí),對(duì)手卻在不斷成長(zhǎng),給喜歡戰(zhàn)斗的玩家以壓力。(
如果只通過(guò)戰(zhàn)斗來(lái)取得游戲的勝利未免有練功機(jī)器之嫌,應(yīng)當(dāng)提供多種成功的樂(lè)趣,不一定非要通過(guò)
戰(zhàn)斗)
??? 需要特別注意得是:這些跟升級(jí)有關(guān)的公式和數(shù)據(jù)需要仔細(xì)記錄,還需要便于后期修改。因?yàn)檫@
些東西直接決定著戰(zhàn)斗和升級(jí),和游戲的可玩性息息相關(guān),加上很難以估算和決定,為了保證可玩性
,需要在后期花大量的時(shí)間修改完善。如果每改動(dòng)一次都需要重新編譯程序,那任何人都會(huì)受不了的
!(我通常的做法是使用文本格式的腳本描述語(yǔ)言)
??? 當(dāng)你完全決定了所有上面這些時(shí),就可以開(kāi)始真正的程序工作了!當(dāng)然你可能發(fā)現(xiàn)漏掉了一些細(xì)
節(jié),但它們的作用不是很關(guān)鍵,我在程序部分再講。
三、程序設(shè)計(jì)
??? 從這一章起,我開(kāi)始講RPG游戲程序部分的具體實(shí)現(xiàn),涉及代碼的部分我主要采用自然描述語(yǔ)
言和類似C的描述語(yǔ)言。相信有一定程序設(shè)計(jì)基礎(chǔ)的人都能看懂!
1.腳本描述語(yǔ)言
(1) 什么是腳本描述語(yǔ)言?為什么要用它?
??? 玩過(guò)很多RPG游戲,打不過(guò)去的地方經(jīng)常用PCT改,有時(shí)候偶然發(fā)現(xiàn)游戲的某個(gè)文件是文本
文件,仔細(xì)閱讀發(fā)現(xiàn)竟然象是某種語(yǔ)言程序,不是匯編,不是PASCAL,也不是C,這究竟是什么呢?
??? 這其實(shí)是游戲制作者自己定義的一種腳本描述語(yǔ)言,制作者一般使用它來(lái)簡(jiǎn)化劇情的設(shè)計(jì)工作。
為什么它能簡(jiǎn)化劇情的設(shè)計(jì)呢,當(dāng)我們了解了腳本描述語(yǔ)言本身后,再來(lái)說(shuō)明這一點(diǎn)。
??? 腳本描述語(yǔ)言是一種解釋語(yǔ)言,解釋語(yǔ)言不同于編譯語(yǔ)言之處就在于它在執(zhí)行之前不需要編譯成
機(jī)器代碼,而是通解釋機(jī)將語(yǔ)句解釋成機(jī)器代碼再執(zhí)行。它的優(yōu)點(diǎn)在于源程序可以方便快捷的修改,
因?yàn)椴恍枰薷慕忉屍鳎砸膊恍枰M(jìn)行編譯。腳本描述語(yǔ)言就是針對(duì)一種某特殊平臺(tái)的描述再這
個(gè)平臺(tái)上特定行為和動(dòng)作的一種解釋語(yǔ)言。游戲的腳本描述語(yǔ)言就是建立游戲本身這個(gè)平臺(tái)上,描述
游戲中的情節(jié)事件的解釋語(yǔ)言。
??? 為什么要用腳本描述語(yǔ)言呢?因?yàn)椋遥校怯螒虻膭∏橥且粋€(gè)龐大而充滿相互聯(lián)系的故事群,
每個(gè)故事可能又由許多小事件組成,這復(fù)復(fù)雜雜的關(guān)系就如同我們?cè)O(shè)計(jì)的程序,函數(shù)就象事件,事件
可以包含事件,函數(shù)也可以包含函數(shù),劇情中我們可以通過(guò)各種選擇實(shí)現(xiàn)不同的分支情節(jié),程序中我
們也可以通過(guò)條件分支來(lái)執(zhí)行不同的程序段。這樣的相似性必然會(huì)讓我們想到用程序語(yǔ)言來(lái)描述劇情
。然而如果我們?cè)谥苯釉谟螒虺绦蛑杏?IF""THEN"之類的語(yǔ)句來(lái)描述劇情,這樣必然使劇
情依附于程序,劇情的改動(dòng)也就意味著程序的改動(dòng),在RPG游戲的制作中,這顯然是不可行的。有
了上面對(duì)腳本描述語(yǔ)言的了解,我們不禁會(huì)這樣想:如果用腳本描述語(yǔ)言呢?在游戲程序中加入解釋
機(jī),專門解釋執(zhí)行描述劇情的腳本語(yǔ)言。這樣在改動(dòng)腳本時(shí),自然就不用改動(dòng)程序了!如此一來(lái),我
們修改劇情時(shí),根本不用在意游戲程序本身,只需要一個(gè)簡(jiǎn)單的文本編輯器就行了,如此帶來(lái)的工作
效率,相信不用我說(shuō)大家也了解了!
(2)腳本描述語(yǔ)言的語(yǔ)法關(guān)鍵字和函數(shù)
??? 腳本描述語(yǔ)言最基本的單位是語(yǔ)句,它應(yīng)當(dāng)具備最基本語(yǔ)法,如表達(dá)式求值(包含各種常用的運(yùn)
算),無(wú)條件轉(zhuǎn)向,條件分支,循環(huán),子函數(shù)等。變量有用戶自定義數(shù)據(jù)也有游戲的全局?jǐn)?shù)據(jù);而描
述稍微復(fù)雜一些的功能可以采用全局函數(shù),這些全局函數(shù)就象C語(yǔ)言的庫(kù)函數(shù)或者是WINDOWS
的API一樣,和各種變量一起在表達(dá)式中引用或者作為其他函數(shù)的參數(shù)。
??? 下面是我制作的RPG游戲的一個(gè)事件腳本文件。
// 示例事件1
say(1,165,"大家好!我是張斌,這是我做的第十七個(gè)游戲。")
say(11,30,"這是一個(gè)測(cè)試!用來(lái)檢驗(yàn)我的游戲引擎。")
say(32,300,"你好!在我這里可以點(diǎn)播MIDI樂(lè)曲。")
choose_flag=choose(4,"請(qǐng)選擇樂(lè)曲:","樂(lè)曲一?樂(lè)曲二?樂(lè)曲三?樂(lè)曲四")
midi(choose_flag)
say(36,30,"小子,你來(lái)找死!")
push
s0=fight(7)
if(s0==1) goto(WIN)
msg("你打輸了了!")
gameover=1
:WIN
pop
msg("你打贏了!")
end
??? 這個(gè)事件的編號(hào)在地圖裝入時(shí)賦值給了一個(gè)NPC,當(dāng)主角接觸到這個(gè)NPC時(shí),這個(gè)事件被觸
發(fā),于是這個(gè)文件被讀入內(nèi)存,開(kāi)始解釋執(zhí)行。我們逐行解釋這個(gè)文件:
第一行? 前面的"http://"同C++語(yǔ)言一樣表示注釋一行。
第二行? 是一個(gè)函數(shù),名稱是 SAY ,有三個(gè)參數(shù),前兩個(gè)是整數(shù)1和165,第三個(gè)是字符"大家好……
"。這個(gè)函數(shù)的意義是在屏幕的縱坐標(biāo)165的位置上顯示人物1(頭像和姓名)的語(yǔ)言"大家好…"
第三行? 同上,在屏幕縱坐標(biāo)30的位置顯示人物11的話"這是一個(gè)測(cè)試……"
第四行? 同上,在屏幕縱坐標(biāo)300的位置顯示人物32的語(yǔ)言"你…"
第五行? choose是一個(gè)選擇函數(shù),在屏幕上出現(xiàn)"請(qǐng)選擇樂(lè)曲"的信息和4項(xiàng)選擇,分別是"樂(lè)曲1","樂(lè)曲2","樂(lè)曲3","樂(lè)曲4",當(dāng)玩家選擇一個(gè)并按下回車后,

玩家選擇的選項(xiàng)號(hào)碼(0表示第一個(gè)
選項(xiàng),1表示第二個(gè),依次類推)作為這個(gè)函數(shù)的返回值賦給變量choose_flag。
第六行? midi是一個(gè)播放MIDI音樂(lè)的函數(shù),它的唯一參數(shù)就是樂(lè)曲號(hào),我們可以看到它的參數(shù)是變量
choose_flag,這就表示根據(jù)choose_flag中的值來(lái)選取播放的樂(lè)曲,而choose_flag中恰恰就放的是
在前一語(yǔ)句中我們選擇的號(hào)碼,因此midi函數(shù)就會(huì)播放我們前面選擇的樂(lè)曲。
第七行? 仍然是人物語(yǔ)言顯示
第八句? 因?yàn)橐M(jìn)入戰(zhàn)斗場(chǎng)景,所以用push函數(shù)將當(dāng)前場(chǎng)景的參數(shù)保存。待戰(zhàn)斗結(jié)束后可以再用pop
函數(shù)取出,借此恢復(fù)戰(zhàn)斗前的場(chǎng)景。
第九句? fight是戰(zhàn)斗事件函數(shù),參數(shù)表示戰(zhàn)斗事件的號(hào)碼,這里表示第7號(hào)戰(zhàn)斗事件。戰(zhàn)斗的結(jié)果
(0表示輸1表示贏)賦值給變量s0
第十句? if語(yǔ)句(也可以理解為函數(shù))對(duì)裝著戰(zhàn)斗結(jié)果的標(biāo)量s0進(jìn)行判斷,如果s0為1(戰(zhàn)斗勝利)
,則執(zhí)行后面的goto函數(shù),跳轉(zhuǎn)到標(biāo)號(hào)為WIN的語(yǔ)句(就是":WIN"那一行),否則繼續(xù)執(zhí)行下面的語(yǔ)
句。
第十一句(s0==0,戰(zhàn)斗失敗)msg函數(shù)表示再屏幕上顯示信息"你打輸了!"
第十二句 給變量gameover賦值為1,處理這個(gè)腳本事件的解釋器檢測(cè)到此變量為1,就終止事件然后
結(jié)束游戲。
第十三句 為作為跳轉(zhuǎn)語(yǔ)句目標(biāo)行
第十四句 pop函數(shù)彈出戰(zhàn)斗前的場(chǎng)景信息,恢復(fù)場(chǎng)景
第十五句 msg顯示信息"你打贏了!"
第十六句 end函數(shù)表示事件結(jié)束
??? 事件結(jié)束后,腳本解釋器會(huì)釋放這段腳本占用的內(nèi)存。
??? 腳本中的"gameover"對(duì)應(yīng)這個(gè)游戲程序中的一個(gè)全局變量"gameover",由于使用了指針,在腳本
中對(duì)它的引用就等同于對(duì)游戲程序中"gameover"的引用。同樣對(duì)應(yīng)于游戲程序中的其他全局變量,也
可以通過(guò)自己定義的腳本變量來(lái)引用。如地圖大小"mapx"和"mapy",當(dāng)前主角位置"cx","cy"等等,
這樣就可以直在腳本語(yǔ)言中引用它們。如if(cx==5&&cy==7)判斷主角是否在地圖(5,7)這個(gè)位置上
if(cx==mapx-1&&cy==mapy-1)?? 判斷主角是否在地圖的角上這段腳本中"say","msg","choose",
"fight","midi"都是描述游戲中情節(jié)的函數(shù),在游戲程序中都有相應(yīng)的函數(shù)與之對(duì)應(yīng),它們有些有
返回值,有些沒(méi)有。這些返回值還可以用來(lái)構(gòu)成表達(dá)式,如:
midi(choose(3,"請(qǐng)選擇樂(lè)曲:","樂(lè)曲一?樂(lè)曲二?樂(lè)曲三")+1)
??? 這個(gè)條語(yǔ)句的含義就成了選擇"樂(lè)曲一"的時(shí)候,實(shí)際放樂(lè)曲二,選擇"樂(lè)曲二"的時(shí)候放樂(lè)曲三,
選擇"樂(lè)曲三"的時(shí)候放樂(lè)曲四。
??? 上面那段腳本中的"if","goto"可以被理解為控制語(yǔ)句也可以被理解成函數(shù)。所有的控制語(yǔ)句函
數(shù)化后,可以使腳本程序程序的格式更加統(tǒng)一,便于閱讀。
??? 同樣對(duì)數(shù)組的引用也可以函數(shù)化,如對(duì)地圖(X,Y)位置的圖案類型的賦值在游戲程序中為
map[x][y]=12,是一個(gè)標(biāo)準(zhǔn)的數(shù)組元素,而在腳本程序中的引用則成了map(x,y)=12,x,y成了函數(shù)
map的兩個(gè)參數(shù)。雖然形式上是函數(shù),但實(shí)際的使用中仍然等同于變量map[x][y](因?yàn)槌绦騼?nèi)部使用
的是指針),因此可以進(jìn)行賦值運(yùn)算。
??? 下面再看一段腳本文件
//示例事件2
say(12,300,"公子,你要買我的寶劍嗎?")
say(1,30,"這寶劍多少錢?")
say(12,300,"30兩銀子!")
say(1,30,"這也太貴了!")
say(12,300,"30兩也嫌貴,這劍可是削豆腐如泥哦!")
say(1,30,"讓我考慮一下!")
choose_flag=choose(2,"買嗎?","買了 不買")
if(choose_flag==1) goto(NoBuy)
if(haveobj(1)<30)? goto(NoMoney)
msg("你花30兩買下了這把破劍!")
say(12,300,"您走好!")
addobj(1,-30)
end
:NoBuy
say(12,300,"小氣鬼,30兩也不肯出!")
end
:NoMoney
say(12,300,"真是個(gè)窮光蛋,快滾!")
end
第一句 仍然是注釋
第二句 到第七句是主角(1)和賣劍的(12)人物對(duì)話
第八句 是選擇"買"還是"不買"
第九句 如果選擇了不買,跳轉(zhuǎn)到:NoBuy
第十句 haveobj對(duì)應(yīng)游戲程序中的函數(shù)haveobj,參數(shù)是物品的種類返回值是擁有這種物品的數(shù)量(
物品1表示銀子,銀子的數(shù)量就是兩數(shù))這一句是判斷主角擁有銀子的數(shù)量如果小于30兩,則跳轉(zhuǎn)
到NoMoney
第十一句 顯示買下劍的信息
第十二句 賣劍的招呼你走好
第十三句 addobj函數(shù)表示為主角增加物品,第一個(gè)參數(shù)為物品的種類(1為銀子),第二個(gè)參數(shù)為
增加的數(shù)量(為負(fù)則是減少)
第十四句 事件結(jié)束
第十五句 不想買劍,跳轉(zhuǎn)到這里
第十六句 賣劍的罵你小氣鬼
第十七句 事件結(jié)束
第十八句 想賣劍但錢不夠,跳轉(zhuǎn)到這里
第十九句 賣劍的讓你滾蛋
第二十句 事件結(jié)束
??? 通過(guò)上面這兩段腳本語(yǔ)言文件,我們可以清楚的了解到腳本語(yǔ)言的的變量對(duì)應(yīng)著游戲程序中的全
局變量、函數(shù)對(duì)應(yīng)著游戲程序中的函數(shù),通過(guò)腳本語(yǔ)言,我們可以輕易的引用游戲中的各項(xiàng)值(主角
屬性,物品,地圖等等),引用游戲中的表述方法(人物對(duì)話,播放音樂(lè),旁白信息等等)。如此以
來(lái)我們構(gòu)建劇情不就輕而易舉了嗎?
??? 然而,我們不能高興的太早了,因?yàn)檎嬲D辛的,才剛剛開(kāi)始!我們下一次將開(kāi)始講如何構(gòu)件腳
本的解釋機(jī)!
(3)解釋機(jī)的編程
??? 在任何編程語(yǔ)言中,表達(dá)式計(jì)算都是最重要的,它在我們的腳本描述語(yǔ)言中同樣存在。因此在腳
本解釋機(jī)中,我們最先實(shí)現(xiàn)的就是表達(dá)式求值。我們?cè)趯?xiě)源程序的時(shí)候進(jìn)行計(jì)算非常簡(jiǎn)單:
??? 如? 3*(3+2)-4
??? 然而在腳本描述語(yǔ)言中,解釋機(jī)所得到的并不是如此簡(jiǎn)單的算式,而是一個(gè)從文本文件中提取的
一個(gè)字符串 "3*(3+2)-4"將這個(gè)字符串轉(zhuǎn)化成一個(gè)數(shù),可不是那么簡(jiǎn)單。如果你認(rèn)真學(xué)習(xí)過(guò)算法,應(yīng)
該能夠從容的實(shí)現(xiàn)它。
??? 我們先看一個(gè)簡(jiǎn)單一點(diǎn)的算式? "32+41*50-2"
??? 我們把自己看成是計(jì)算機(jī),從字符串的左邊開(kāi)始掃描,當(dāng)掃描到'3'時(shí),可以知道這是一個(gè)數(shù)的
開(kāi)始,將它記如入一個(gè)空閑的字符串的第一位buf[0],當(dāng)我們掃描到'2',它仍然是個(gè)數(shù)字,是我們
正在記錄這個(gè)數(shù)的新的一位,我們將它放入buf[1],當(dāng)我們掃描到'+'時(shí),它不是數(shù)字了,我們也就
知道第一個(gè)數(shù)讀完了,在記錄它的字符串的下一個(gè)位置buf[2]放入字符串結(jié)束標(biāo)志'0'我們得到的這
個(gè)數(shù)放在buf中,是"32",通過(guò)一些編程系統(tǒng)提供的字符串轉(zhuǎn)整型數(shù)的函數(shù),我們可以將這個(gè)字符串
轉(zhuǎn)化為數(shù)值,即使你用的編程系統(tǒng)沒(méi)有這個(gè)函數(shù),根據(jù)數(shù)字字符的ASCII碼值,我們自己也可以
很容易實(shí)現(xiàn):
?? '0',"1","2"...'9'的ASCII碼值分別為 48~57,如果一個(gè)數(shù)字字符的ASCII碼為n,則
它代表的數(shù)字值為n-48。如一個(gè)數(shù)char str[5]={"2341"},它的值就可以寫(xiě)成
(str[0]-48)*1000+(str[1]-48)*100+(str[2]-48)*10+str[3]-48于是我們可以寫(xiě)出下面的將字符串
轉(zhuǎn)變?yōu)檎麛?shù)的C語(yǔ)言函數(shù)(其他語(yǔ)言也類似)
int stoi(char *str)???????? //str是以0為結(jié)束的字符串
{
int return_value=0,i=0;
while(str{i}!=0)?????????? //字符串結(jié)束
?? return_value=return_value*10+str[i++]-48;
return(return_value);
}
??? 知道了這個(gè)"32"是32,我們將它記下(裝入一個(gè)變量),再繼續(xù)往下掃描,直到字符'4',我們
知道我們得到了一個(gè)"+",它是一個(gè)二元運(yùn)算符號(hào),我們接著向下掃描利用上面掃描32的方法,我們
得到了另一個(gè)數(shù)41,我們現(xiàn)在知道了32+41,考慮一下,我們?cè)谑裁辞闆r下能將它們相加:
??? 要么是在41后字符串結(jié)束,要么緊接著41的是一個(gè)比'+'優(yōu)先級(jí)低或相等的運(yùn)算符,如'-'。將它
們相加后得到73,我們就回到了剛剛得到31的那一步。
??? 如果41后面跟著的是一個(gè)更高優(yōu)先級(jí)的運(yùn)算符,如本例中的'*',我們必須先進(jìn)行這個(gè)乘法運(yùn)算
,那只好先將31和'+'保存起來(lái),接著向下掃描,我們又得到了50和其后的運(yùn)算符'-',判斷的方法和
剛才一樣,因?yàn)?*'比'-'的優(yōu)先級(jí)高,所以我們可以放心的先將41*50算出,得到2050。這時(shí)我們現(xiàn)
在所掃描到的算式就成了32+2050-2,我們?cè)俅伪容^運(yùn)算符'+'和'-',優(yōu)先級(jí)相同,我們就可以先算
32+2050了,得到2082,我們繼續(xù)向后掃描,了解到在2082-2后字符串結(jié)束,我們可以繼續(xù)計(jì)算最后
一步2082-2=2080,最后這個(gè)字符串表達(dá)式的結(jié)果就是2080。
??? 現(xiàn)在我們?cè)賮?lái)看看括號(hào):如? 3*(3==2+2*5)-4這個(gè)式子,在讀完3*之后我們讀到得不是一個(gè)數(shù),
而是一個(gè)括號(hào),這時(shí)我們需要先算括號(hào)內(nèi)的式子,所以的先將3和*保存起來(lái),比較==和+,得先計(jì)算
加法,先將3==保存,再來(lái)比較+和*,先計(jì)算2*5得到10,因?yàn)橄旅嬉粋€(gè)等到算完2*5得到10,因?yàn)楹?br />面是括號(hào),所以要取出先前保存的數(shù)和運(yùn)算符進(jìn)行計(jì)算,但一直要取到前一個(gè)括號(hào),但我們順序存了
3*、3==、和2+,怎么知道前一個(gè)括號(hào)在那里呢?方法是在遇到前括號(hào)時(shí)也保存括號(hào)的標(biāo)記,這樣的
話,我們算到這一步時(shí)所保存的順序?yàn)椋?*,(,3==和2+,我們遇到一個(gè)后括號(hào),就取出以前保存的
數(shù)進(jìn)行運(yùn)算,先算2+10得12,再算3==12得0,這時(shí)取出了括號(hào)(,我們這才知道這個(gè)括號(hào)內(nèi)得運(yùn)算完
結(jié),現(xiàn)在的算式剩下了3*0-4,再比較*和-,先算3*0得0,最后得結(jié)果就是0-4得-4。
??? 在上面的運(yùn)算中,我們?cè)谟龅礁邇?yōu)先級(jí)的運(yùn)算時(shí),需要將前面的數(shù)值和運(yùn)算符保存,但我們并不
太清楚需要保存幾次,如這兩個(gè)算式:
??? 1=2==3+4*5??? 1+2+3+4+5
??? 它們?cè)谟?jì)算過(guò)程中需要保存的的數(shù)和運(yùn)算符個(gè)數(shù)是不同的,前一個(gè)需要先算4*5結(jié)果為20,再算
3+20結(jié)果為23,再算2==23結(jié)果為0,再算1=0(在一般的語(yǔ)言中,象這樣給常數(shù)賦值是禁止的,但在
我們腳本語(yǔ)言的運(yùn)算中,為了保持一致性,我們?cè)试S這樣的式子,但賦值被忽略),最多情況下需要
要保存三個(gè)數(shù)和運(yùn)算符。而后一個(gè)式子一個(gè)也不用保存,從左到右依次運(yùn)算就行了。
??? 我們發(fā)現(xiàn)這些數(shù)和運(yùn)算符的使用都是"先存的后用,后存的先用",這不是堆棧嗎?對(duì)!我們就
用堆棧來(lái)保存它們。
??? 堆棧的實(shí)現(xiàn)很多軟件書(shū)中都已經(jīng)講過(guò),心中有數(shù)的讀者自然可以跳過(guò)下面這一段。
??? 一般實(shí)現(xiàn)堆棧可以用鏈表或數(shù)組,我們犯不上用鏈表這么復(fù)雜的數(shù)據(jù)結(jié)構(gòu),用比較簡(jiǎn)單的數(shù)組就
可以了。對(duì)于使用C++的朋友可以用類的形式來(lái)實(shí)現(xiàn)
class STACK???????????? //整數(shù)堆棧
{
??? int *stack;???????? //存放數(shù)據(jù)的首地址
??? int p;????????????? //堆棧位置指示(也可以用指針)
??? int total;????????? //預(yù)先定義的堆棧大小
? public:
??? STACK(int no);????? //指定堆棧大小的構(gòu)造函數(shù)
??? ~STACK();?????????? //析構(gòu)函數(shù)
??? int push(int n);??? //壓棧
??? int pop(int *n);??? //出棧
};
STACK::STACK(int no)
{
? total=no;
? stack=new int [total];
? p=0;
}
STACK::~STACK()
{
? delete[] stack;
}
int STACK::push(int n)?? //壓棧
{
? if(p>total-1)
???? return(0);
? else
??? stack[p++]=n;
? return(1);
}
int STACK::pop(int *n)??? //出棧
{
? if(p<1)
??? return(0);
? else
??? *n=stack[--p];
? return(1);
}
??? 如果用C也是一樣,使用initSTACK來(lái)聲明一個(gè)堆棧,但要記著在用完之后調(diào)用freeSTACK釋放內(nèi)

typdef struct STACK
{
?? int *stack;???????? //存放數(shù)據(jù)的首地址
?? int p;????????????? //堆棧位置指示(也可以用指針)
?? int total;????????? //預(yù)先定義的堆棧大小
};
int initSTACK(struct STACK *stk,int no)
{
? stk=(struct STACK *)malloc(sizeof(STACK));
? stk->total=no;
? stk->p=0;
? stk->stack=new int [total];
//如果stack不為零表示分配成功,堆棧初始化也就成功
? if(stk->stack)
??? return(1);
? free(stk);???? //如果失敗釋放內(nèi)存
? return(0);
}
void freeSTACK(struct STACK *stk)
{
? if(stk)
?? {
??? delete[] stk->stack;
??? free(stk);
?? }
}
int pushSTACK(struct STACK *stk,int n)?? //壓棧
{
? if(stk->p>stk->total-1)
???? return(0);
? else
??? stk->stack[stk->p++]=n;
? return(1);
}
int popSTACK(struct STACK *stk,int *n)??? //出棧
{
? if(stk->p<1)
??? return(0);
? else
??? *n=stk->stack[--p];
? return(1);
}
??? 可以看出這種堆棧類在聲明對(duì)象時(shí)要給出堆棧的大小,對(duì)于我們的表達(dá)式求值來(lái)說(shuō),100個(gè)單
元足夠了。但有人不禁會(huì)想到,上面這些都是整數(shù)堆棧,對(duì)于運(yùn)符怎么存儲(chǔ)呢?其實(shí)是一樣的,我們
可以給運(yùn)算符編上用整數(shù)序號(hào)來(lái)代表,這樣就可以利用整數(shù)堆棧來(lái)保存了。給運(yùn)算符編號(hào)的另一個(gè)好
處是可以利用運(yùn)它的高位來(lái)代表運(yùn)算符的優(yōu)先級(jí)!如下面一個(gè)函數(shù)將字符串運(yùn)算符轉(zhuǎn)化成含優(yōu)先級(jí)的
序號(hào),只要比較這些序號(hào)高位值的大小就可以得出誰(shuí)得優(yōu)先級(jí)高了。(下面這個(gè)函數(shù)只對(duì)二元運(yùn)算符
編號(hào),沒(méi)有處理一元和多元,因?yàn)樗鼈兌伎梢杂枚\(yùn)算表示。)
int convert_mark(char *str)
{
//優(yōu)先級(jí)高
? if(strcmp(str,"*")==0) return(240);?? //0xf0
? if(strcmp(str,"/")==0) return(241);?? //0xf1
? if(strcmp(str,"%")==0) return(242);?? //0xf2
? if(strcmp(str,"+")==0) return(224);?? //0xe0
? if(strcmp(str,"-")==0) return(225);?? //0xe1
? if(strcmp(str,"<<")==0) return(208);? //0xd0
? if(strcmp(str,">>")==0) return(209);? //0xd1
? if(strcmp(str,"<")==0) return(192);?? //0xc0
? if(strcmp(str,"<=")==0) return(193);? //0xc1
? if(strcmp(str,">")==0) return(194);?? //0xc2
? if(strcmp(str,">=")==0) return(195);? //0xc3
? if(strcmp(str,"==")==0) return(176);? //0xb0
? if(strcmp(str,"!=")==0) return(177);? //0xb1
? if(strcmp(str,"&")==0) return(160);?? //0xa0
? if(strcmp(str,"^")==0) return(144);?? //0x90
? if(strcmp(str,"|")==0) return(128);?? //0x80
? if(strcmp(str,"&&")==0) return(112);? //0x70
? if(strcmp(str,"||")==0) return(96);?? //0x60
? if(strcmp(str,"=")==0) return(80);??? //0x50
? if(strcmp(str,"+=")==0) return(81);?? //0x51
? if(strcmp(str,"-=")==0) return(82);?? //0x52
? if(strcmp(str,"*=")==0) return(83);?? //0x53
? if(strcmp(str,"/=")==0) return(84);?? //0x54
? if(strcmp(str,"%=")==0) return(85);?? //0x55
? if(strcmp(str,">>=")==0) return(86);? //0x56
? if(strcmp(str,"<<=")==0) return(87);? //0x57
? if(strcmp(str,"&=")==0) return(88);?? //0x58
? if(strcmp(str,"^=")==0) return(89);?? //0x59
? if(strcmp(str,"|=")==0) return(90);?? //0x5a
//優(yōu)先級(jí)低
}
??? 在RPG得腳本描述語(yǔ)言中,我們基本用不上小數(shù),因此我們?cè)趯?shí)際的二元運(yùn)算中得到的將是三
個(gè)整數(shù),其中兩個(gè)是參與運(yùn)算的數(shù),另一個(gè)是運(yùn)算符的序號(hào),我們還得對(duì)此編出進(jìn)行運(yùn)算的函數(shù)。如

//運(yùn)算求值 n1是第一個(gè)參加運(yùn)算得數(shù),n2是運(yùn)算符號(hào)得序號(hào)
//n3是第二個(gè)參加運(yùn)算的值
int quest(int n1,int n2,int n3)
{
? int ret=0;
? switch(n2)
? {
??? case 240:ret=n1*n3;break;? // "*"?? 乘法
??? case 241:ret=n1/n3;break;? // "/"?? 除法
??? case 242:ret=n1%n3;break;? // "%"?? 求余數(shù)
??? case 224:ret=n1+n3;break;? // "+"?? 加法
??? case 225:ret=n1-n3;break;? // "-"?? 減法
??? case 208:ret=n1<<n3;break; // "<<"? 左移
??? case 209:ret=n1>>n3;break; // ">>"? 右移
??? case 192:ret=n1<n3;break;? // "<"?? 小于
??? case 193:ret=n1<=n3;break; // "<="? 小于等于
??? case 194:ret=n1>n3;break;? // ">"?? 大于
??? case 195:ret=n1>=n3;break; // ">="? 大于等于
??? case 176:ret=n1==n3;break; // "=="? 等于
??? case 177:ret=n1!=n3;break; // "!="? 不等于
??? case 160:ret=n1&n3;break;? // "&"?? 與
??? case 144:ret=n1^n3;break;? // "^"?? 異或
??? case 128:ret=n1|n3;break;? // "|"?? 或
??? case 112:ret=n1&&n3;break; // "&&"? 邏輯與
??? case 96:ret=n1||n3;break;? // "||"? 邏輯或
??? case 90:ret=n1|n3;break;?? // "|="
??? case 89:ret=n1^n3;break;?? // "^="
??? case 88:ret=n1&n3;break;?? // "&="
??? case 87:ret=n1<<n3;break;? // "<<="
??? case 86:ret=n1>>n3;break;? // ">>="
??? case 85:ret=n1%n3;break;?? // "%="
??? case 84:ret=n1/n3;break;?? // "/="
??? case 83:ret=n1*n3;break;?? // "*="
??? case 82:ret=n1-n3;break;?? // "-="
??? case 81:ret=n1+n3;break;?? // "+="
??? case 80:ret=n3;break;????? // "="?? 賦值
??? case -1:ret=n3;break;????? // 用來(lái)表示前括號(hào)
??? case? 0:ret=n1;break;????? // 空運(yùn)算
? }
? return(ret);
}
??? 我們可以看到,在上面得有關(guān)賦值得運(yùn)算中,我們實(shí)際上并沒(méi)有進(jìn)行賦值,因?yàn)槲覀冞€沒(méi)有任何
變量來(lái)接受賦值,下一次里我們?cè)賮?lái)講講將游戲中的數(shù)據(jù)作為變量進(jìn)行運(yùn)算和賦值,這可是最激動(dòng)人
心的哦!
注意:解釋機(jī)并不是獨(dú)立的軟件程序,它是游戲源程序的一部
????? 分,只有這樣腳本解釋語(yǔ)言它才可能通過(guò)它引用到游戲
????? 中的變量和函數(shù)。
??? 為了達(dá)到引用游戲中變量和函數(shù)的目的,我們專門定制一個(gè)函數(shù),用來(lái)將字符串轉(zhuǎn)變成整數(shù)(假
如起名為val,則它的函數(shù)原型就是int val(char *str))假若輸入字符串是一個(gè)數(shù)字串,我們就可
以調(diào)用前面一講講過(guò)的將數(shù)字字符串轉(zhuǎn)變?yōu)檎麛?shù)的函數(shù)將它轉(zhuǎn)化為數(shù)值;如果輸入字符串的第一個(gè)字
符是英文字母或者下劃線,我們就根據(jù)這個(gè)字串返回它所代表的游戲中的變量。
??? 例如,我們?cè)谟螒虺绦蛑卸x了主角當(dāng)前的位置是放在int cur_x,cur_y 當(dāng)中,我們可以約定在
當(dāng)在腳本語(yǔ)言中也用cur_x和cur_y來(lái)代表這兩個(gè)變量(只所以用同形的字串,是為了便于記憶,當(dāng)然
你也可以給用另外的字串代替),假若我們的這個(gè)函數(shù)得到的輸入字串是"cur_x",我們就讓val函數(shù)
返回它變量cur_x中的值。如果是"cur_y",我們就返回變量cur_y 的值。同樣象人物屬性、物品等等
,都可以約定的字符串形式引用。但對(duì)于數(shù)組元素呢?我們?cè)冢谜Z(yǔ)言中都是采用跟在變量后的方括號(hào)
內(nèi)寫(xiě)入數(shù)組索引值,采用方括號(hào)的目的是在編譯時(shí)區(qū)別數(shù)組和函數(shù)。但在解釋語(yǔ)言中步存在區(qū)別的問(wèn)
題,所以象BASIC 都采用和函數(shù)相同的圓括號(hào)。所以我們?cè)谔幚頂?shù)組和函數(shù)時(shí)也基本相同如:
?????? addobj(12,100)
?????? map(23,32)
??? 前一個(gè)是函數(shù),表示給主角加100個(gè)物品12(12是物品代號(hào))后一個(gè)是二維數(shù)組,表示地圖上某
一點(diǎn)的物體,相當(dāng)于map[23][32]。
??? 假若輸入的字串不是數(shù)字串,我們就可以將它拆分處理:如將"addobj(12,100)"分為"addobj"、
"12"、"100",共三項(xiàng)。對(duì)于cur_x就只得到一項(xiàng)"cur_x",根據(jù)它們的第一項(xiàng),我們可以知道它們代
表的是那個(gè)變量或函數(shù),拆分出的其他項(xiàng)就是數(shù)組的索引或函數(shù)的參數(shù),因此我們可以很容易的指定
val的返回值(對(duì)于函數(shù)就是函數(shù)的返回值,對(duì)變量就是變量值)。
??? 但如果圓括號(hào)內(nèi)不僅僅是常數(shù),而且有變量或者函數(shù),或者是由函數(shù)變量組成的表達(dá)式,如:
?????? addobj(map(23-cur_x,32)+1,100)
這樣又怎么辦呢?解決方法就是交叉的遞歸調(diào)用。
我們現(xiàn)在所做的一切最終就是為了將一個(gè)字符串表達(dá)式轉(zhuǎn)變成一個(gè)整數(shù),寫(xiě)成函數(shù)的形式就是(假設(shè)
函數(shù)名為cal)
???? int cal (char *str);
???? 如果輸入?yún)?shù)為1+addobj(map(23-cur_x,32)+1,100)+1,
???? 它需要調(diào)用val 函數(shù)來(lái)求參加運(yùn)算的一個(gè)數(shù)值
????? addobj(map(23-cur_x,32)+1,100)
??? 而在val 函數(shù)中,對(duì)于addobj(map(23-cur_x,32)+1,100)會(huì)拆分出"map(23-cur_x,32)+1"這樣的
項(xiàng),它也是一個(gè)表達(dá)式,我們只有以它為參數(shù)再次調(diào)用cal求值。cal又會(huì)調(diào)用val求值
val("map(23-cur_x,32)"),val拆分得到"23-cur_x",,再次調(diào)用cal("23-cur_x"),cal再調(diào)用
val("cur_x")得到cur_x的值,再返回給前一個(gè)調(diào)用它的cal,如此逐曾返回,最終由cal求得真正的
結(jié)果。
??? cal 調(diào)用 val,val又去調(diào)用cal,這就形成了交叉的遞歸調(diào)用,第一次調(diào)用
cal("1+addobj(map(23-cur_x,32)+1,100)"),第二次調(diào)用cal為cal("map(23-cur_x,32)+1")時(shí),第
三次調(diào)用為cal("23-cur_x"),遞歸調(diào)用一個(gè)函數(shù)時(shí),系統(tǒng)會(huì)自動(dòng)的為它開(kāi)辟內(nèi)存,生成一個(gè)副本,
這三次調(diào)用就同三個(gè)不同的函數(shù)一般。
??? 如此一來(lái),在復(fù)雜的表達(dá)式我們也能求出起結(jié)果了,但需要注意的一點(diǎn)是遞歸調(diào)用太多時(shí)耗費(fèi)系
統(tǒng)資源太多,也容易出錯(cuò),我們應(yīng)當(dāng)盡量避免。如在val中,如果對(duì)輸入串拆分得到的項(xiàng)是數(shù)字串自
然不用去調(diào)用cal,如果拆分得到單獨(dú)的變量或函數(shù),如"map(cur_x,32)"經(jīng)拆分得到的"cur_x"這一
項(xiàng),因?yàn)闆](méi)有運(yùn)算,則直接遞歸調(diào)用自己val("cur_x")就可以了,而不用再調(diào)用cal了,這樣就可以
減少遞歸調(diào)用的層次,減少內(nèi)存消耗和發(fā)生錯(cuò)誤的可能性。
??? 我們可以發(fā)現(xiàn),到目前為止,我們可以引用游戲中的變量和函數(shù)了,但我們還不能給游戲中的變
量賦值,也就是說(shuō)我們?cè)谀_本語(yǔ)言中能夠知道主角位置、主角的屬性、所處的位置、擁有的物品等等
,但卻不能改變它們,因?yàn)槲覀兊馁x值運(yùn)算并沒(méi)有真正對(duì)游戲中的這些變量賦值,這將使腳本描述語(yǔ)
言大失光彩,沒(méi)關(guān)系,我們將在下一講解。
??? 首先我們分析我們以前進(jìn)行的工作,
??? STACK(類或結(jié)構(gòu))用來(lái)暫存運(yùn)算式中的數(shù)字和運(yùn)算符代號(hào)
??? convert_mark 用來(lái)給運(yùn)算符編含有優(yōu)先級(jí)的代號(hào)
??? quest??????? 用來(lái)計(jì)算兩個(gè)整數(shù)運(yùn)算的結(jié)果
??? val????????? 用來(lái)將字符串(整數(shù),變量,函數(shù))轉(zhuǎn)化成
???????????????? 整數(shù)結(jié)果
??? cal????????? 將字符串表達(dá)式轉(zhuǎn)化成整數(shù)
??? 如果你看過(guò)以前的幾講,應(yīng)該很容易搞清楚這些函數(shù)的基本調(diào)用關(guān)系。
???????? +--> STACK
???????? |
???????? +--> convert_mark
??? cal--|
???????? +--> quest
???? |?? |
???? |?? +--> val ---+
???? |?????????????? |
???? +---------+-----+
??? 我們現(xiàn)在再來(lái)考慮一下對(duì)游戲中的變量賦值,最先想到的自然是再進(jìn)行實(shí)際運(yùn)算的函數(shù)quest 中
實(shí)現(xiàn),但quest 函數(shù)的輸入?yún)?shù)只是三個(gè)整數(shù),即使我們知道現(xiàn)在進(jìn)行的是賦值運(yùn)算,知道了運(yùn)算的
結(jié)果,但卻無(wú)法知道應(yīng)該將結(jié)果賦值給那個(gè)變量。我們又會(huì)想到只在val 函數(shù)中才有可能拆分出代表
變量的字符串,才知道是那個(gè)變量,但在這個(gè)函數(shù)中我們并不知道這個(gè)變量參加的是何種運(yùn)算,也不
知道運(yùn)算的結(jié)果,所以也沒(méi)有辦法進(jìn)行賦值。所以我們只有將希望放在調(diào)用它們兩個(gè)的函數(shù)cal 上了
,因?yàn)槭莄al 調(diào)用的val,所以cal能得到運(yùn)算的類型和結(jié)果,至于參加賦值運(yùn)算的那個(gè)具體的變量,
因?yàn)関al 函數(shù)返回的是這個(gè)變量的值,因此我們還不能確定進(jìn)行運(yùn)算的是那個(gè)變量,但如果將val 的
返回值改為指向這個(gè)變量的指針,我們不是既能引用又能賦值了!我們根據(jù)參加運(yùn)算變量指針?biāo)赶?br />的值調(diào)用quest 函數(shù)就可以得到運(yùn)算結(jié)果,我們?cè)俑鶕?jù)這個(gè)運(yùn)算是否賦值運(yùn)算再?zèng)Q定是否將這個(gè)結(jié)果
寫(xiě)入這個(gè)變量的指針。
??? 需要注意的是,在val 函數(shù)中,如果判斷出是變量,我們就返回它的指針就行,但如果是整數(shù)或
者是函數(shù),它們并沒(méi)有指向其值的指針,但我們可以定義一個(gè)靜態(tài)(static)變量,用來(lái)存放這個(gè)整數(shù)
或者函數(shù)的結(jié)果,然后返回指向這個(gè)靜態(tài)變量的指針就行了。(注意是靜態(tài)變量,因?yàn)樗诤瘮?shù)結(jié)束
后不釋放內(nèi)存。如果是一般的動(dòng)態(tài)變量,在函數(shù)結(jié)束后就會(huì)釋放,我們返回的指針指向的就是一個(gè)不
確定的值了!)當(dāng)然你也可以采用全局變量。(因?yàn)樗谡麄€(gè)程序執(zhí)行期間都不釋放內(nèi)存)。
??? 完成這幾個(gè)函數(shù),我們的字符串表達(dá)式求值部分就完成了,但我們的解釋機(jī)并沒(méi)有完成,我們還
需要無(wú)條件轉(zhuǎn)移、判斷、條件轉(zhuǎn)移、循環(huán)一些控制語(yǔ)句,這些我們會(huì)在下一講中完成!首先為了快速的解釋執(zhí)行,我們一般都將整個(gè)腳本文件讀入內(nèi)存,將腳本語(yǔ)

句一行的一行儲(chǔ)存。因?yàn)橐苿?dòng)內(nèi)存指針
可比移動(dòng)文件指針?lè)奖憧焖俣嗔恕?br />首先我們來(lái)看語(yǔ)句注釋,我們可以采用";" "http://","*"等注釋一行,也可以用成對(duì)的"/*"和"*/注釋一
整段。它的實(shí)現(xiàn)很簡(jiǎn)單,我們只要在將腳本文件讀入內(nèi)存時(shí)將這些行忽略就行了。(遇到這種注釋一
行的標(biāo)志,就讀到此行末尾,但不在內(nèi)存中保存。遇到是注釋一段的起始標(biāo)志,就一直讀到注釋一段
的結(jié)束標(biāo)志,這其中讀入的并不在內(nèi)存中保存!)
??? 首先我們來(lái)看看語(yǔ)句跳轉(zhuǎn),很自然的,可以通過(guò)指定語(yǔ)句在腳本文件中的行號(hào)來(lái)進(jìn)行跳轉(zhuǎn)(注意
不是BASIC中語(yǔ)句前的行號(hào)),但這樣做法實(shí)現(xiàn)很簡(jiǎn)單,但對(duì)于腳本文件的編制和修改就麻煩大
了,因?yàn)槟惚卦S自己數(shù)出想要跳轉(zhuǎn)到的那一行的行號(hào),而這其中又要排除忽略掉的注釋行,而當(dāng)你每
次修改時(shí)刪除或者增加一行,那么相關(guān)的跳轉(zhuǎn)又要重新數(shù)行號(hào)。這恐怕會(huì)使人喪失編制腳本文件的耐
心。
??? 解決這個(gè)問(wèn)題較佳的辦法是,在想要跳轉(zhuǎn)的那一行前加一個(gè)標(biāo)號(hào)行,跳轉(zhuǎn)時(shí)指定哪個(gè)標(biāo)號(hào)就行了
(結(jié)構(gòu)化BASIC語(yǔ)言、匯編、C語(yǔ)言都是如此),在將這些腳本讀入內(nèi)存時(shí)忽略這些標(biāo)號(hào)行,但
記錄下它的標(biāo)號(hào)名稱和行號(hào),再根據(jù)每個(gè)跳轉(zhuǎn)中指定的標(biāo)號(hào)名稱,將它們的參數(shù)轉(zhuǎn)化成行號(hào)。如:
假設(shè)腳本文件是這樣的:
xxxxx??????????? //行0
xxxxxxx????????? //行1
//此行是注釋,讀入內(nèi)存時(shí)忽略
xxxxx??????????? //行2
:label?????????? //標(biāo)號(hào)行讀入內(nèi)存時(shí)忽略
xxxxxxx????????? //行3
xxxxxx?????????? //行4
xxxxxx?????????? //行5
xxxxxx?????????? //行6
xxxxxx?????????? //行7
xxxxxx?????????? //行8
goto(label)????? //行9
xxxxxxx????????? //行10
xxxxxx?????????? //行11
/*
這其中的內(nèi)容都是注釋,讀入內(nèi)存時(shí)忽略
xxxxx
xxxx
*/
xxxxxxx????????? //行12
xxxxxx?????????? //行13
xxxxxxx????????? //行14
end????????????? //行15
讀入內(nèi)存并修改跳轉(zhuǎn)參數(shù)后變?yōu)?br />xxxxx
xxxxxxx
xxxxx
xxxxxxx
xxxxxx
xxxxxx
xxxxxx
xxxxxx
xxxxxx
goto(3)
xxxxxxx
xxxxxx
xxxxxxx
xxxxxx
xxxxxxx
end
注意到其中g(shù)oto的參數(shù)變成了想要跳轉(zhuǎn)到的那一行在內(nèi)存中的行號(hào),而注釋和標(biāo)號(hào)行都被忽略了。
??? 我們現(xiàn)在再講講如何具體實(shí)現(xiàn):
??? 首先將腳本文件中的每一行讀入內(nèi)存(當(dāng)然要忽略注釋),當(dāng)讀入的是標(biāo)號(hào)行時(shí)(此行第一個(gè)字
符是':' ),將所有的標(biāo)號(hào)名稱和下一行的行號(hào)保存起來(lái)(規(guī)定標(biāo)號(hào)名的長(zhǎng)度和數(shù)量,比如規(guī)定變量
名少于32的字符,每個(gè)腳本文件中的標(biāo)號(hào)不超過(guò)100個(gè),標(biāo)號(hào)名稱可以順序保存在一個(gè)二維字符數(shù)組
中,如
????? char label[100][32]
行號(hào)也順序放入一個(gè)整數(shù)數(shù)組,如
????? int labelno[100]
??? 當(dāng)腳本文件讀入內(nèi)存后,再次掃描這些內(nèi)存,如果遇到goto就比較goto后的參數(shù)和label中的內(nèi)
容依次比較,如果相同,就將goto后的內(nèi)容改變成labelno內(nèi)相應(yīng)的行號(hào)。
??? 如此一來(lái),我們就得到了最終的內(nèi)存結(jié)果。
??? 當(dāng)內(nèi)存中的這些腳本解釋時(shí),我們會(huì)用一個(gè)變量來(lái)放當(dāng)前即將執(zhí)行的行號(hào),如果執(zhí)行完一行,就
將這個(gè)變量加1,然后下次就選取這個(gè)變量指示的那一行語(yǔ)句執(zhí)行。在進(jìn)行跳轉(zhuǎn)時(shí),只要把這個(gè)變量
改變?yōu)間oto后的行號(hào)即可。
??? 當(dāng)然,goto(xx)的形式我們也可以把它當(dāng)作函數(shù)處理,在我們前面講過(guò)的val 函數(shù)中,遇到goto
時(shí)將當(dāng)前的命令行號(hào)變?yōu)閤x即可。
??? 這次主要講解釋機(jī)中對(duì)注釋語(yǔ)句和轉(zhuǎn)向語(yǔ)句的實(shí)現(xiàn)方法,下一次我們?cè)趤?lái)講條件分支、循環(huán)等等

??? 條件分支我們可以采用類似匯編語(yǔ)言的方法,在解釋機(jī)內(nèi)設(shè)置一個(gè)判斷專用的標(biāo)志變量(
if_flag),根據(jù)if(...)括號(hào)內(nèi)的表達(dá)式設(shè)置這個(gè)變量。然后then(....)再根據(jù)這個(gè)變量的值決定是
否轉(zhuǎn)向括號(hào)內(nèi)指定的標(biāo)號(hào)行(這些都是在前面講過(guò)的函數(shù) val里實(shí)現(xiàn)),如:
??? if(cur_x<10)?? //條件成立設(shè)置判斷標(biāo)志為1,反之為0
??? xxxxxx
??? xxxxxx
??? then(label1)?? //判斷標(biāo)志位為1則轉(zhuǎn)向label1否則繼續(xù)
??? xxxxxx
??? xxxx
??? :label1
??? xxxxxx
??? 我們?cè)谧x入腳本進(jìn)內(nèi)存時(shí)時(shí),同goto一樣也要將then括號(hào)中的標(biāo)號(hào)轉(zhuǎn)變?yōu)橄鄳?yīng)的行號(hào)。
??? 這樣我們就可以和匯編語(yǔ)言一樣,結(jié)合其他變量實(shí)現(xiàn)循環(huán)。
??? s1=0?????????? //給循環(huán)記數(shù)器設(shè)置初值
??? :label1
??? xxxxxxx??????? //需要循環(huán)執(zhí)行的語(yǔ)句
??? xxxxxx???????? //需要循環(huán)執(zhí)行的語(yǔ)句
??? s2=s1*10?????? //需要循環(huán)執(zhí)行的語(yǔ)句
??? xxxxxx???????? //需要循環(huán)執(zhí)行的語(yǔ)句
??? s1+=1????????? //循環(huán)記數(shù)器自動(dòng)增加
??? if(s1<10)????? //判斷循環(huán)是否結(jié)束
??? then(label1)?? //如果沒(méi)有結(jié)束跳轉(zhuǎn)到label1
??? xxxxx????????? //如果結(jié)束了繼續(xù)執(zhí)行下面這些行
??? 另外還要說(shuō)明的一點(diǎn)是,在RPG游戲時(shí)我們經(jīng)常會(huì)遇到彈出有多項(xiàng)選擇。比如說(shuō)在買東西的時(shí)
候,會(huì)列出多個(gè)物品讓你選擇。我們把這多項(xiàng)選擇也做成函數(shù):
??? int choose(char *str,int n,char *item,int must)
??? 就拿前面買東西來(lái)說(shuō),你在一個(gè)武器店的地圖中放置一個(gè)店老板的NPC,他對(duì)應(yīng)的腳本如下:
??? say(12,30,"您好,歡迎光臨本店!")
??? :ask?????? //詢問(wèn)您要什么
??? s1=choose("您要?",3,"買東西 賣東西 不要了",1)
??? if(s1==0)
??? then(buy)? //跳轉(zhuǎn)到買東西
??? if(s1==1)
??? then(sell) //跳轉(zhuǎn)到賣東西
??? //不買也不賣
??? say(12,30,"不要了?您慢走!")
??? end
??? :buy??????????? //買物品
??? s1=choose("買什么?",5,"匕首 竹劍 鋼劍 梭鏢 銅錘",0)
??? xxxxxxxxxx????? //根據(jù)選擇的選項(xiàng)s1
??? xxxxxxxxxxx???? //給玩家增加物品,減少金錢等等
??? xxxxxxxxx?????? //
??? xxxxxxx???????? //
??? xxxxxxxxxx????? //
??? if(s1==-1)????? //如果選擇"買什么"時(shí)點(diǎn)了ESC退出
??? then(buy)?????? //放棄買物品跳轉(zhuǎn)到"您要什么"的選擇
??? goto(ask)?????? //買了一件物品后繼續(xù)選擇要買的物品
??? :sell?????????? //賣物品
??? s1=chooseobj()? //從自己有的物品中選擇一樣
??? if(s1>100)????? //判斷物品的種類
??? then(nosell)??? //決定是否跳轉(zhuǎn)到nosell
??? xxxxxxxx??????? //根據(jù)玩家選擇的物品s1
??? xxxxxxxxx?????? //減物品,加金錢等等
??? xxxxxx????????? //
??? if(s1==-1)????? //選擇要賣的物品時(shí)點(diǎn)了ESC鍵退出
??? then(ask)?????? //放棄賣物品跳轉(zhuǎn)到"您要什么"的選擇
??? goto(sell)????? //賣完一件,選擇還要賣的
??? :nosell???????? //不收這種物品
??? say(12,30,"抱歉,這種東西我們不收!")
??? goto(sell)????? //繼續(xù)選擇要賣的物品
這其中的choose函數(shù)是我們?cè)谟螒虺绦蛑袑?shí)現(xiàn)的一個(gè)多項(xiàng)選擇的函數(shù),以s1=choose("您要?",3,"買
東西 賣東西 不要了",1)為例
s1內(nèi)放置的選項(xiàng)號(hào)碼(第一個(gè)是0,第二個(gè)是1,依次類推)
??? "您要?"是多項(xiàng)選擇時(shí)的提示
??? 3是選項(xiàng)的個(gè)數(shù)
??? "買東西 賣東西 不要了"是三個(gè)供玩家選項(xiàng),中間以' '分隔
??? 1是強(qiáng)制玩家必須從這些選項(xiàng)中選擇一個(gè),不能按ESC鍵放棄選擇(此時(shí)返回-1給s1),如果是
0則可以按ESC鍵放棄選擇。
??? 另外 chooseobj()也是我們?cè)谟螒蛑袑?shí)現(xiàn)的一個(gè)函數(shù)。從玩家的物品中選擇一樣,返回它在玩家
物品匣中的位置,它在地圖行走、戰(zhàn)斗中的物品使用都可以使用。
??? 前面的八篇講了有關(guān)RPG游戲腳本解釋機(jī)的實(shí)現(xiàn),從這篇起,我們就開(kāi)始從一個(gè)更高的位置對(duì)游
戲做統(tǒng)籌!
一、首先我們來(lái)看看一般RPG游戲的大體包括的模塊。
?? 1 系統(tǒng)初始化/結(jié)束:(system_init/system_exit)
??? 在游戲進(jìn)行之前,我們必須進(jìn)行一些系統(tǒng)初始化的工作,象什么讀取圖片、音樂(lè)等數(shù)據(jù),對(duì)屏幕
、聲卡等硬件做初始化工作等等;同樣,在游戲結(jié)束后,我們還需要進(jìn)行一些整理工作,象是關(guān)閉設(shè)
備驅(qū)動(dòng)、釋放內(nèi)存等等。
?? 2 標(biāo)題畫(huà)面:(logo)
??? 功能為顯示游戲標(biāo)題,選擇菜單"新的游戲\讀取進(jìn)度\退出游戲"。并根據(jù)不同的選項(xiàng)分別調(diào)用
不同的模塊。
??? 有時(shí),我們不需要另外做開(kāi)始新游戲的模塊,我們只要專門做一個(gè)游戲開(kāi)始狀態(tài)的進(jìn)度,象讀取
進(jìn)度一樣讀取它,就可以完成這個(gè)功能。譬如:游戲最多能保存5個(gè)進(jìn)度,從game1.sav到game5.sav,
我們事先做好一個(gè)進(jìn)度為game0.sav,保存游戲初始的狀態(tài),玩家在讀取進(jìn)度時(shí)可以用通過(guò)
load_game(int n)調(diào)用(n=1,2,3,4,5)。當(dāng)開(kāi)始新游戲時(shí)則通過(guò)load_game(0)調(diào)用game0.sav。
?? 3 讀取/保存進(jìn)度:(load_game/save_game)
??? 讀取就是從文件中讀出一些數(shù)據(jù)裝入游戲的變量中,保存就是將游戲中的變量保存到文件中。當(dāng)
然你也可以指定一個(gè)算法在在數(shù)據(jù)讀入和寫(xiě)入之前進(jìn)行變換,從而起到對(duì)進(jìn)度加密的效果。一般需要
保存的數(shù)據(jù)有:主角狀態(tài)、主角所在地圖、 npc狀態(tài)、其他數(shù)據(jù),這些可以根據(jù)游戲具體情況進(jìn)行取
舍。另外進(jìn)度的保存在游戲中進(jìn)行,讀取則在標(biāo)題畫(huà)面或者游戲進(jìn)行中都行。(當(dāng)然使用劇情腳本的
話,你甚至可以通過(guò)和某個(gè)npc交談或者使用某件物品來(lái)保存進(jìn)度。)
?? 4 游戲進(jìn)程: (run_game)
??? 一般是一個(gè)較大的循環(huán),在循環(huán)中處理玩家的按鍵、控制npc的運(yùn)動(dòng)、處理其他實(shí)時(shí)的事件、顯
示地圖等等。
二、模塊的運(yùn)行關(guān)系
??? 游戲運(yùn)行時(shí),首先進(jìn)行系統(tǒng)設(shè)置system_init(),然后調(diào)用標(biāo)題畫(huà)面i=logo(),如果i==0即玩者
選擇"新的游戲",那么開(kāi)始新游戲load_game(0),然后進(jìn)行游戲run_game();如果i==1即選擇"舊
的進(jìn)度"則選擇進(jìn)度號(hào)l=choose_progress(),如果l==0返回標(biāo)題畫(huà)面。如果1<=n<=5則讀取進(jìn)度
load_game(l),然后再進(jìn)行游戲run_game();如果i==2即玩者選擇"退出游戲",則調(diào)用結(jié)束模塊
system_exit(),然后結(jié)束程序。
??? 當(dāng)然在游戲進(jìn)行過(guò)程中run_game()中,也可以讀取進(jìn)度load_game(l)和保存進(jìn)度save_game(l);
三、其它
??? 這些模塊中,除了游戲進(jìn)程模塊run_game外,都比較容易實(shí)現(xiàn),所以我們就略過(guò)不講,今后著重
講有關(guān)run_game的部分。
??? 世界是在不停運(yùn)動(dòng)改變著的,我們用游戲所創(chuàng)造的虛擬世界也是這樣,這種不斷的運(yùn)動(dòng)在我們的
程序中就體現(xiàn)為循環(huán)。程序?qū)嶋H上是一種在計(jì)算機(jī)和用戶之間進(jìn)行交互的工具,為了響應(yīng)用戶的控制
,程序需要了解用戶的輸入信息,并把通過(guò)對(duì)計(jì)算機(jī)的控制做出相應(yīng)的響應(yīng)。但程序怎樣了解用戶的
輸入信息呢?那就需要我們的程序主動(dòng)的對(duì)用戶用來(lái)輸入信息的硬件(如鍵盤、鼠標(biāo)、游戲桿等等)
進(jìn)行檢測(cè)。
??? 用戶可能在任何時(shí)候輸入信息,因此程序也必須隨時(shí)準(zhǔn)備接收這種輸入,在程序中接受輸入的兩
種方法有兩種:一種是程序完全停止下來(lái)準(zhǔn)備接收,直到接收到數(shù)據(jù)才開(kāi)始繼續(xù)運(yùn)行;另一種是程序
以一定的頻率在不斷的循環(huán),如果發(fā)現(xiàn)有輸入信息,就對(duì)輸入進(jìn)行處理,如果沒(méi)有輸入信息時(shí),程序
就繼續(xù)循環(huán)并處理其他的事情。(就向tc里的bioskey(0)和bioskey(1),或者是windows編程中
GetMessage和PeekMessage)
??? 注意:上面這兩種方法的劃分,是完全從編程的角度來(lái)看的,
????????? 即從某個(gè)函數(shù)或者方法來(lái)看的。實(shí)際上在硬件或者更
????????? 低級(jí)的機(jī)器語(yǔ)言中,輸入的接收是完全采用循環(huán)檢測(cè)
????????? 實(shí)現(xiàn)的。
??? 顯而易見(jiàn),第一種方法有它的局限性,它是一種單向不可逆的交互過(guò)程,在需要用戶一步步輸入
信息的簡(jiǎn)單程序中比較適用,但在需要雙向交互的實(shí)時(shí)程序中卻難以適應(yīng)。試想在動(dòng)作或者射擊類游
戲中,等待玩家每次按鍵后才運(yùn)動(dòng)、攻擊的敵人是多么的愚蠢可笑呀!(我原來(lái)就在APPLE-II上做過(guò)
這樣的游戲)
??? 因此第二種方法才是游戲運(yùn)行中關(guān)鍵的輸入接收方法。也就是說(shuō),當(dāng)玩家不進(jìn)行輸入操作時(shí),程
序的循環(huán)就會(huì)去執(zhí)行其他的事情,如控制敵人運(yùn)動(dòng)、攻擊等等,而不是停下來(lái)等你輸入。
??? 當(dāng)然,我們?cè)谟螒蛑幸残枰绦蛲O聛?lái)等待輸入的時(shí)候,比如"請(qǐng)按任意鍵 press any key...
"就是我們經(jīng)常使用它的地方。
??? 上面講的這些并不是廢話,因?yàn)樵谟螒蛑写_需要區(qū)分這兩種輸入方法,正確的使用它們才能達(dá)到
你預(yù)期的效果。
??? 比如:在 RPG游戲中,人物對(duì)話顯示一屏后,就會(huì)等待玩家按鍵看下一屏對(duì)話。這時(shí)我們就可以
采用第一種方法,將程序完全停下來(lái)等待按鍵,也可以在玩家沒(méi)有按鍵的時(shí)候在人物對(duì)話框的下方閃
爍著顯示一個(gè)按鍵的圖形,提示玩家按鍵。這時(shí)就需要采用上面提到的第二種方法。在游戲中這樣的
細(xì)節(jié)很多,你完全可以自己決定采用什么的方法以達(dá)到什么樣的效果。
??? 我們的游戲主體,實(shí)際上就是在不斷地處理著這樣的用戶輸入、并對(duì)它做出響應(yīng)的一個(gè)大的循環(huán)
體。有了這一概念,我們?cè)趯?duì)它進(jìn)行設(shè)計(jì)時(shí),就容易多了。
??? 循環(huán)結(jié)構(gòu)開(kāi)始
????? --處理NPC
????? --檢測(cè)用戶按鍵
????? --如果是方向鍵,進(jìn)行主角移動(dòng)處理。如果觸發(fā)事件,進(jìn)
??????? 行事件處理。
????? --如果是Esc鍵,彈出游戲菜單,根據(jù)玩家選擇處理。
????? --刷新屏幕(可以設(shè)定獨(dú)立的定時(shí)事件完成)
??? 循環(huán)結(jié)構(gòu)結(jié)束?
??????? 注意,其中的屏幕刷新,是由屏幕刷新率決定的,可以設(shè)置獨(dú)立的定時(shí)事件來(lái)完成,也可以
放在這個(gè)主循環(huán)內(nèi)進(jìn)行。具體的實(shí)現(xiàn)方法,我們下次再講。