<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    游戲策劃咨訊
    做一個游戲并不難,難的是做一個好游戲;完美在于積累!

    隨著手機普及率的提高以及附加功能越來越豐富,被稱為“拇指一族”的手機玩家的數量也急劇攀升,形成了一個相當特殊的消費族群。日前,記者在“空中網”的會客室里見到國內最大手機在線游戲“奪寶中華”的頭號玩家陳劍(化名)。這位被稱做“中國手機游戲第一人”的傳奇玩家,給人的第一印象并不太起眼:三十來歲的年紀,略顯瘦削的身材,一臉疲憊的神情。

    -最適合我的娛樂方式

    ——“它沒有富麗堂皇的畫面和音效,但它是一種可以移動的娛樂。”

    作為某名牌大學的畢業生,陳先生眼下在深圳擁有一家化工貿易公司。由于生意上的緣故,他使用手機的歷史可以追溯到10年前,當時他就已經是中國移動的大客戶了。

    三年多以前,陳先生首次接觸手機游戲,起初只是感覺好奇,并沒有怎么上癮。直到在不久后加入了空中網的“奪寶中華”才真正被它吸引住了。

    與那些瞄準運營商提供的豐厚獎品的玩家不同,對于每天為生意奔波的陳先生來說,手機在線游戲只是最適合他的一種娛樂方式,“與PC在線游戲不同,它沒有富麗堂皇的畫面和音效,但它是一種可以移動的娛樂,不論到哪里,只要能接收到GPRS的信號,就可以玩,很方便。”

    -兩年手機費花了三萬元

    ——“我只是想讓自己的生活充實起來,談不到什么'喪志'。”

    后來,陳先生越玩越入迷,曾經創下了連續18個小時在線的紀錄——“那時,一睜眼就是'奪寶'”。在短短的兩年中,他光是在手機資費方面就花去了3萬多元。

    陳先生說,今年的前五個月里,“奪寶中華”在搞全國排名,他幾乎每天打十六七個小時,名次自然是突飛猛進。但后來,陳先生有一個多月中在游戲里無所事事,“因為我能完成的任務還沒有在游戲中開放呢。”

    在“奪寶”的兩年里,陳先生為了在游戲中保持自己的領先地位,連續換了五部手機。如今,他每天都帶三部手機在身邊,每一部隨時都可以上線。

    大量的金錢投入再加上體力透支,生活和事業必然會受到一定的影響。“在玩得最瘋的那一段,如果有訂單來,我都是讓別人去接,非要我去的,我就今天拖明天,明天拖后天……真是耽誤了一些生意。”不過,他并不同意有人批評他“玩物喪志”,“我如今雖然不算非常富有,但也有一定的經濟基礎了,我只是想讓自己的生活充實起來,談不到什么喪志。至于為此損失的,我會想辦法彌補。”

    -游戲中交的朋友無話不談

    ——在“奪寶中華”排名前十位的玩家基本上都是廣東人。

    既然是一種在線交流的游戲方式,自然少不了會結交到一些朋友,而這也與游戲用戶所在的區域有關。根據“空中網”的統計顯示,在所有“奪寶中華”的注冊玩家中,大多都來自南部沿海發達的經濟城市,尤以廣東、福建、浙江的手機用戶最多。

    陳先生說,他所接觸的玩友大多都與他的年齡、經歷相仿,雖然事業都小有成就,但精神卻十分空虛,“極無聊的那種”。在游戲中,大家找到了許多的樂趣,很快就成為了無話不談的好友。據他介紹,如今在“奪寶中華”排名前十位的玩家基本上都是廣東人,年齡都在30來歲。他們曾在線下進行過交流,但主要還是通過電話聯系,基本上沒有見過面。

    -高級玩家手機號能賣上萬元

    ——“我的號碼曾經被開價到8000元,現在估計怎么也上萬了。”

    隨著游戲樂趣逐漸減少,許多玩家都會選擇放棄游戲,而手機中那個積攢的豐富經驗值和道具裝備的虛擬角色,便成了不折不扣的“雞肋”。另一方面,又總有些低級別的玩家想“不勞而獲”,而這些“雞肋”便自然而然地成為了他們渴求的目標。

    有需求,就會有交易。和PC在線游戲一樣,手機玩家之間也會為此進行私下的交易。據說在“奪寶中華”中,一個高等級的注冊號碼可以賣到千元以上。“我的一個朋友就把他的號碼賣了3000多元,”陳先生說:“我的號碼曾經也被開價到8000元,現在估計怎么也上萬了!”                       

    付出的比收獲多

    由于任務開放的局限和升級越來越困難,陳先生現在的游戲時間已大幅減少,每天大約只有六七個小時。即便這樣,他在游戲中也依然是級別最高的王者,向他挑戰成功便意味著新的傳奇的誕生。為此,他經常能碰到成群結伙前來叫板的玩家。有一次甚至曾同時與10名玩家對陣,多虧自己的幫派兄弟及時趕到,才將對手打得狼狽收場。

    在采訪過程中,記者發現陳先生的手一直在微微顫抖,而且還時不時地望望擺在桌子上的手機。當他在講話中揮動右手時,手掌下部清晰地顯出一塊凹陷的痕跡,他戲稱是奪寶“奪”出來的老繭。看來,手機游戲早已和他生活中的方方面面融為一體了。

    玩游戲到了較高的等級,手機號就可以賣出少則上千、多則上萬的高價。但陳先生表示對此不屑一顧:“我說他們是學別人裝大款罷了。其實,就是真出這么多錢,我也絕對不賣的。畢竟是自己的心血結晶,多少個日日夜夜不眠不休,豈是這點兒錢能換來的?”

    記者問到玩手機游戲最大的收獲是什么陳先生笑了笑:“肯定是付出的更多一些。”他婉轉地表示,由于平時工作的壓力太大,沒有一個穩定的排解渠道,而手機游戲恰恰帶給了他一種舒緩情緒的方式。“平時在生活中,你付出和得到的往往不會成正比;而在游戲中,只要你肯付出、肯花精力,就一定能成功。”而這份超越現實的滿足感,也許就是他最大的收獲。
    posted @ 2005-02-19 22:34 藍色雪焰 閱讀(399) | 評論 (0)編輯 收藏
     

             記得第一次見到FishMan是在清華大學南門旁邊的一個餐館里,當時新浪的好幾個版主也都在,Fishman請客,第一印象就是很帥,不大象一個軟件公司的CEO,到象是電影明星。他的真名吳錫桑,讀出來怪怪的,感覺遠不如FishMan這個網名好記。FishMan自己也很喜歡這個網名,有時候還戲稱自己為“漏網之魚”,那次聊天的時候,Fishman談到自己曾經做過4年的水木清華BBS的技術欄目版主,談到這些,眼神和語氣中透出更多的自信,他說很多朋友都是那時認識的,包括3721的CTO周鴻一,至今,現在水木清華的BBS上還保留著很多他的技術文章。

        不過現在FishMan已經成功的將自己的角色轉變為了一個CEO,他現在領導的天下網絡軟件公司正積極的擴展自己網絡游戲的地域,而自己也從一個埋頭于編程的開發人員轉變為了經常穿梭于各地各大公司之間的決策和管理人。Fishman是一個真正從軟件開發人員一步步轉變為CEO,其中的過程和艱辛也只有他自己最清楚。

        初露鋒芒

        FishMan來自廣東的海濱城市汕尾,91年進入暨南大學計算機科學系。剛進大學 參觀計算中心的時候,算是第一次見到真正的計算機。此前因為喜歡玩街邊的 電子游戲機而報考的電腦專業。比起一進機房就能刷拉刷拉撥弄鍵盤的同班同學,他覺得簡直有些無地自容。然而很快便投入到了對電腦的熱情當中,對這些復雜的東西很感興趣,整天泡在系里的電腦機房,而且慢慢地有了超前意識,開始在圖書館查閱《計算機世界》,《電腦》和《中國計算機用戶》電腦報刊等,并把學到的內容進行實踐。剛開始是依樣畫葫蘆,后來漸漸地有了自己的思維。倒了1994年,隨著Windows編程和多媒體技術的興起,出現了許多各種題材的多媒體光盤。他開始研究使用原型開發工具技術, 開發一套類似Macromedia Authorware的《南粵多媒體開發平臺》,可以用這個工具制作多媒體光盤,軟件演示和多媒體課件等等。經過半年的努力,Fishman順利完成了《南粵多媒體開發平臺》的1.0版本。幾年的努力終于有了點收獲。并為此獲得了廣東省科委組織專家鑒定的1995年廣東省高校杯軟件比賽的第一名,并獲得計算機世界獎學金。后來他的這個軟件還代表代表暨南大學參加1995年12月在武漢大學舉行的“挑戰杯”全國大學生競賽,并取得了廣東省高校歷年來最好成績。

        那個時代,同很多年輕人一樣,求伯君,朱崇軍作為成功的程序員也是FishMan的偶像,他們都懷著自己創業的激情。單槍匹馬闖天下,畢業后,一個香港的富商投資30萬,幫助FishMan在1995年底就開辦了自己的公司:廣州飛鷹電腦公司。而這個公司的也成為了Fishman再一次的轉折點,同時也成為一段崎嶇路程的開始。

        創業的艱辛

        當時,飛鷹電腦公司是一家不足10個人的微型IT公司。由于資金的限制,他只能用最少的錢去做最好的事。Fishman已經開始擔當的不僅僅是程序員的角色了。他不但要做技術,而且要策劃市場推廣,銷售,代理合作,技術支持。雖然很勞累,但他能夠頂得主,但投資方派來的一個在國家單位混日子的50來歲的干部,一個連IT怎么拼寫都不知道的總經理確使他領略到了創業的艱辛和無法忍受的束縛。

        Fishman很清楚每個版本的軟件都有它的生命周期和不足,看著Internet的逐漸普及,97年初他就開始提議開發飛鷹4.0版,增加Internet特性,遭到總經理的否決。有幾個公司和單位看上他們的產品,希望能投資飛鷹電腦公司,遭到總經理的否決。

        在飛鷹3.0版獲得1997年12月第三屆中國PC應用軟件設計大獎賽優勝軟件, 名列工具類軟件第一,并因此獲得了中國軟件行業協會理事的職位的時候,IDG派人前來飛鷹電腦公司洽談,希望投資進來, 要求提供一份商業計劃書,再次遭到總經理的否決!

        更為強烈的打擊接踵而至,公司為了其他的事情決定暫停飛鷹4.0版的開發。這下使得本來就勞累過度的FishMan無法承受,一怒之下病倒了。并于98年7月離開了飛鷹電腦公司。后來他提到這段痛苦的經歷將他帶入了更深層次的思考之中,開始靜下心來思考中國軟件產業的問題。他認為:中國模式的資金與技術的合作,受傷的總是技術人員。很多著名的程序員都吃過這個虧,并且提到了周鴻偉、王志東等等。中國的很多軟件企業,仍然擺脫不了原始的作坊模式,原始的管理模式。幾個人找臺電腦找個地方就是一家軟件公司,能真正走向成功的少之又少。中國的軟件產品,仍然只是僅有的幾個品種。 在國外微軟等航空母艦型企業的唑唑進逼和盜版小販的搖旗吶喊之下,很多軟件企業在中國IT史上閃了一下就消失了。那時的Fishman極其痛苦,親眼身邊搞技術的朋友一個個去了美國,不禁嘆息:難道只有美國才是軟件技術人員的樂土嗎?

        網絡使Fishman開始了第二次創業

        網絡給了程序員很大的機遇和挑戰,很多公司和個人趕上了網絡的大潮,便成就了一番事業,而有很多人并沒有意識到網絡帶給程序員的機會,于是現在還是程序員。其實對于程序員,對技術發展方向的敏感性決定了是否能夠在轉折的時候把握住這一切。程序員是關注技術的,但不等同于只有技術。

        網絡風暴開始悄無聲息的刮了起來,99年1月,FishMan加盟了剛成立的博大國際互聯網公司,成為了博大公司的第二位員工。不久之后,在他的帶領下,博大推出了郵件列表引擎,調查引擎,域名轉向和網絡辭海等產品。

        偶然的一個機會,Fishman遇到了中文熱訊的創始人之一的陳仲文,陳仲文問FishMan為什么要出國,他說:“我需要一個新的起點,不想再做作坊式開發的軟件。我需要一個軟件公司,有足夠的開發人員,規范化的開發模式和國際營運模式的背景。希望把軟件公司做大,效益做好”。可以說,陳仲文是一個讓Fishman感受到機會就在我身邊的人。他建立中文熱訊的時候,根本就不看好。幾個人弄了不到一萬元拼命做沒有技術含量的網頁,被拷貝和取代的可能性很大,也就是說,成功的概率太小。可是他們成功了,先是被IDG注資,后來合并成夢想熱訊集團。

        二人的想法非常的一致,安裝這個考慮,他們擬定了兩份商業計劃書。1999年11月,成功的從香港Myrice總公司吸引了投資,創立了仙童數碼科技有限公司(CNTOMI.COM)。沒有過多久,另外一份創辦網絡游戲軟件的商業計劃書也得到了投資,這就是后來成為了國內最大的網絡圖形MUD游戲開發商天夏科技有限公司。在仙童數碼,他的角色是CTO,負責代表公司技術形象、策劃公司產品的開發、技術難題攻關、規范開發流程、技術員工的招聘、合作廠 商的聯系、生產部門(開發部,產品部,QA部)的日常管理。

        在仙童數碼,他開始將自己的理念灌注其中,建立規范,公司的部門架構設立了之后,每個職位都準備一份工作職責范圍,日常工作內容和工作規范。把如何做好該工作崗位,如何書寫工作報告,如何制訂工作計劃等等的內容都通過規范文檔詳細做好。他強調是的團隊作戰能力,市場部及產品部攜手負責產品開發的前期準備工作,開發部準備需求分析、技術可行性分析和系統分析文檔,然后提供工作文檔給質檢部和產品部。開發部負責程序編寫,質檢部準備測試文檔,產品部設計網站界面,技術支持準備維護手冊。編碼和測試交替進行,當產品質量達到要求后,才發布新的軟件產品。研究部門對目前潮流的新技術進行深入研究,并編寫技術可行性報告和制作一些軟件樣品,最后上交給公司的管理層進行決策。

        轉眼之間一年過去了,FishMan仍然在尋求著突破自我,看著自己一手帶大的仙童數碼更加不斷的成熟和發展,他的內心也充滿著另外一種渴望。FishMan賣掉了自己在仙童數碼的股份,轉到了同樣是自已一手參與創建起來的從事網絡游戲開發的天夏科技有限公司并擔任CEO。

        在Fishman眼中,CEO不僅僅是一種稱謂,而是同國際企業接軌的一種稱呼,做CEO,不是一個名字的改變,而是從整個軟件公司的具體運作上要按照國際標準。走出國門,才能真正了解國外的現狀,才能做一個真正的CEO。他認為在這方面,其實也適用于程序員,雖然只要是編程,無論多么偏僻遙遠,都可以稱為程序員,但如果能夠真正在技術上和國外接軌,從軟件開發規范上學習國外的標準。

        Fishman曾經說過:“網絡給了我們很多的機會,但是一直不敢去涉足真正的網絡公司,他總覺得軟件公司更真實,做技術更保險,更加適合自己,無法放棄對軟件開發的情結。但是他還是非常感激網絡的,網絡給了軟件更多的機會,無論是個人還是軟件產業。”他還是無法完全放下程序員對技術的鐘情。     程序員關心的是軟件工程,技術方案,編程技巧和協同工作等等,目標是做個技術高手。壓力來自技術的推陳出新,技術難題的解決,保證程序的質量和開發進度等等。這些都是可以通過自身的努力解決的,也就是說程序員自己可以把握的。管理者關心的是軟件工程與質量控制,技術壁壘,市場切入點,成本控制,市場推廣,銷售渠道,政府政策,版權保護,吸納人才,售后服務等等,目標是讓公司獲取收益,得到生存和發展的空間。他的壓力來自公司的目標和發展機會,需要協調公司內部的運轉和外部的關系。特別是公司小的時候抗風險能力很差,CEO要協調公司的資金,人員,市場和定位的矛盾。不容易把握,努力了也不一定有所改善,一不小心就會翻船。兩者的轉變是從局部考慮問題到全局考慮問題。一個成功是程序員-->項目主管-->開發經理-->CTO-->CEO這么過渡過來的。

        雖然已經取得了很多成績,但FishMan一直也不敢放松,對于自己一路拼搏的征程,感嘆的說道:“驀然回首,投身于中國軟件行業已經五年了。我曾經滿腔熱血,以不眠不休的編程為樂;曾經意氣風發,登上銀光閃耀的領獎臺;曾經深受打擊,想漂洋過海逃避失意;也曾經峰回路轉,再次創業風起云涌。一切的一切,豈是言語所能表達。在飽經風霜之后,不變的只有對軟件產業的信念!”

    posted @ 2005-02-18 22:29 藍色雪焰 閱讀(235) | 評論 (0)編輯 收藏
     
    使用MIDP(Mobile Information Device Profile)的開發人員經常會抱怨用些什么辦法才可以在一個MIDlet上顯示動畫。MIDP 1.0 沒有直接提供對動畫的支持(正在開發中的MIDP 2.0支持),但真要是自己去實現,其實也并非是一件很難的事。

    任何動畫的最基本的前提,是要在足夠快的時間內顯示和更換一張張的圖片,讓人的眼睛看到動的畫面效果。圖片必須按照順序畫出來。從一張圖片到下一張圖片之間的變化越小,效果會越好。

    首先要做的,是使用你的圖片處理軟件(比如ps或者firework)創建一系列相同大小的圖片來組成動畫。每張圖片代表動畫一幀。

    你需要制作一定數量的禎--越多的幀會讓你的動畫看上去越平滑。制作好的圖片一定要保存成PNG(Portable Network Graphics) 格式,MIDP唯一支持的圖片格式;(有兩個辦法讓你剛做好的圖片在MIDlet上變成動畫。第一,把圖片都放到一個web服務器上,讓MIDlet下載他們,MIDP內置的HTTP支持。第二個辦法更簡單,把圖片用MIDlet打包成jar文件。如果你使用的是J2ME開發工具,把PNG文件放在你的項目文件里面就可以了。

    動畫的過程其實更像帳本記錄:顯示當前幀,然后適當地更換到下一幀。那么使用一個類來完成這個工作應該是很恰當的,那好,我們就先定義一個AnimatedImage類:

    import java.util.*;
    import javax.microedition.lcdui.*;
    // 定義了一個動畫,該動畫其實只是一系列相同大小的圖片
    // 輪流顯示,然后模擬出的動畫
    public class AnimatedImage extends TimerTask {;
    private Canvas canvas;
    private Image[] images;
    private int[][] clipList;
    private int current;
    private int x;
    private int y;
    private int w;
    private int h;

    // Construct an animation with no canvas.

    public AnimatedImage( Image[] images ){;
    this( null, images, null );
    };

    // Construct an animation with a null clip list.

    public AnimatedImage( Canvas canvas, Image[]
    images ){; this( canvas, images, null );
    };

    // Construct an animation. The canvas can be null,
    // but if not null then a repaint will be triggered
    // on it each time the image changes due to a timer
    // event. If a clip list is specified, the image is
    // drawn multiple times, each time with a different
    // clip rectangle, to simulate transparent parts.

    public AnimatedImage( Canvas canvas, Image[] images,
    int[][] clipList ){;
    this.canvas = canvas;
    this.images = images;
    this.clipList = clipList;

    if( images != null && clipList != null ){;
    if( clipList.length < images.length ){;
    throw new IllegalArgumentException();
    };
    };

    if( images != null && images.length > 0 ){;
    w = images[0].getWidth();
    h = images[0].getHeight();
    };
    };

    // Move to the next frame, wrapping if necessary.

    public void advance( boolean repaint ){;
    if( ++current >= images.length ){;
    current = 0;
    };

    if( repaint && canvas != null && canvas.isShown()
    ){;
    canvas.repaint( x, y, w, h );
    canvas.serviceRepaints();
    };
    };

    // Draw the current image in the animation. If
    // no clip list, just a simple copy, otherwise
    // set the clipping rectangle accordingly and
    // draw the image multiple times.

    public void draw( Graphics g ){;
    if( w == 0 || h == 0 ) return;

    int which = current;

    if( clipList == null || clipList[which] == null
    ){;
    g.drawImage( images[which], x, y,
    g.TOP | g.LEFT );
    }; else {;
    int cx = g.getClipX();
    int cy = g.getClipY();
    int cw = g.getClipWidth();
    int ch = g.getClipHeight();

    int[] list = clipList[which];

    for( int i = 0; i + 3 <= list.length; i +=
    4 ){;
    g.setClip( x + list[0], y + list[1],
    list[2], list[3] );
    g.drawImage( images[which], x, y,
    g.TOP | g.LEFT );
    };

    g.setClip( cx, cy, cw, ch );
    };
    };

    // Moves the animation's top left corner.

    public void move( int x, int y ){;
    this.x = x;
    this.y = y;
    };

    // Invoked by the timer. Advances to the next frame
    // and causes a repaint if a canvas is specified.

    public void run(){;
    if( w == 0 || h == 0 ) return;

    advance( true );
    };
    };

    posted @ 2005-02-17 20:04 藍色雪焰 閱讀(349) | 評論 (0)編輯 收藏
     
    游戲策劃
    (轉貼自
    http://www.gamemaker.com.cn
    1.什么是游戲策劃?

      我們今天的主題是游戲策劃。因此,我們應該首先明白一個問題,什么是游戲策劃?曾經有朋友拿著自己的一些故事來找我們,覺得自己的故事非常感人,如果作出游戲來會非常轟動。如此說來,寫一些感人的故事是否是游戲策劃?就我們自身的例子來說,我們感到一個好的故事離一個好的游戲相差實在是太遠了。我們的《水滸英雄傳—火之魂》是根據中國古典名著改編的,為了融合現代人的觀點和口味,對原著的改編非常之大,就我們本身的故事腳本而言,可以說看了都說好。但做成游戲后就是另一回事了。且不說玩家的評論,單說我們自己都感到沒有完全表達出腳本的內涵。并非用好的原料就能作出一道好菜。

      這不是說我們的制作人員的水平問題,而實實在在是人與人之間的思維和感受的不同所造成的。對一個故事而言,并非所有人都有同樣的感受和評論。即使是一部電視劇,不同的人就會有不同的評論。就更不用說將一個故事給了七、八個甚至更多的不同個性不同思想的人,讓他們做出一個讓成千上萬人欣賞的故事,其結果是可想而知的了。講這些的目的是使有興趣于此的朋友注意一點----一個故事對一個游戲而言,只是萬里長征的第一步。

      當然,有一些游戲根本就與故事扯不上邊,如模擬類、體育類游戲大多于此。還有一些朋友拿著一厚沓腳本來與我們商量,把他們的腳本制作出來。在他們的腳本中有詳細的故事、菜單設計、游戲規則設計,表面上非常全面。他們管這個叫做游戲策劃。也就是說他們認為寫了一個故事,設計了一些菜單的功能,制訂了一些游戲的規則,就是游戲策劃。如果真的是這樣,事情就變得簡單多了。

      我相信,看這篇文章的朋友都抱著各種心態----好奇!看一看游戲制作的內幕!或者是自己想嘗試一下。別人的游戲玩多了,總覺得自己有一些好的想法,絕對能策劃個好游戲。但如果你真是想策劃一個游戲,并且真是想有人能夠制作出來的話,那么,我們有一個非常重要的前題是----誰來做這個游戲!!!你千萬不要指望不顧程序和美術部分,而將故事、菜單、規則制訂下來之后,你就萬事大吉了。如果那樣的話,你肯定得不到自己的作品,當程序和美術人員完成了工作的時候,拿給你的東西將與你想的完全不一樣。你會看到----那根本不是同一個游戲。反復說這些話的原因,就是想告訴對此感興趣的朋友們,千萬不要以為游戲策劃只是一個編故事、制訂規則。那么作為一個游戲策劃人,應該具備什么樣的素質呢?什么樣的人才有資格成為游戲策劃呢/ 


    2.游戲策劃人應該具備什么條件?

      怎么樣?被我前面的話嚇住了吧?這也不是,那也不是。什么樣的人才是游戲策劃人呢?

      現在我再告訴你,一個理想的游戲策劃人應該具備如下條件:

    A.玩的游戲足夠多,并且對各個游戲有過一番研究。因為只有這樣,才能對自己的游戲進行充分的設計,才能揚長避短。也只有玩過足夠多的游戲才能明白玩家的一些約定俗成的東西,比如YES、NO按鈕誰在左面?誰在右面?如果反了,有些玩家會經常誤操作。

    B. 對程序的制作十分精通,精通游戲圖形和音樂編程及人工智能。這樣,你就能充分地發揮程序的作用,在不增加工作量的情況下,使游戲變得更加有趣。比如說在場景中有滿街亂走的小人兒,如果不考慮更多的人工智能,這些人只是簡單地隨機亂走的話,一百個人和兩個人幾乎是用相同程序處理的。但如果兩個人情況下,其中有一個人在一定的條件下,需要去一次酒館的話,給程序方面增加的工作量,比增加一百人可大多了。而為游戲最后的測試又增加了許多工作。你必須創造必要的條件,看他去不去酒館。而如果這個條件是隨機出現的話,你不走運的話,可能會為此等上一天。

    C.對美術部分十分精通,精通各種平面、三維和動畫軟件,具有非常扎實的美術基礎。并對攝影和鏡頭剪接有充分的了解。如果你真的具備這一點,你就能使參與游戲的其他美術人員充分了解游戲的整體感覺。你可以告訴他們整個游戲的色調是偏暖或是偏冷。如果是卡通的話,是采用迪斯尼的卡通風格還是日本的卡通漫畫家的風格?你可以親自設計出游戲的主要角色的人物形象定位和場景效果圖,這樣有經驗的美術人員會充分體會你所要表現的游戲氣氛。是歡快活潑的趣味風格,還是陰風陣陣的恐怖風格。

    D.對音樂十分精通,精通樂理并擅長古今中外各種風格的音樂。由于人們對音樂有著極強的感受和理解能力,所以一個有氣氛的場景應該配合相同氣氛的音樂。或是利用一種特殊的音樂效果表達特殊的氣氛。我們曾經試圖使一個音樂制作人明白我們為什么想將中國古典音樂與現代搖滾音樂相結合,但最后終于沒有成功。

    E. 有深厚的文學功底和豐富的中外歷史知識,最好也能明白一些表達心理學方面的知識。這樣你就能設計出動人的角色對話----表現動人情節的一個最重要的法寶,也有助于你烘托游戲時代背景。如果想設計科幻背景的游戲,你還必須熟悉未來科技的一些知識和趨勢。

      也許這是一個游戲策劃的標準人才,但這樣的人才我們從來還沒有遇到過。因為一個人的一生是有限的,在有限的時間里不可能在各個方面都能得到充分的發展。沒辦法,只能依靠我們自己來解決了。幸好我們也許在上述幾點的其中一點非常突出。那么就讓象你我這樣的人來策劃游戲吧。但我們能夠成功,因為我們擁有兩件必勝法寶----謙虛好學和不懈的努力。現在讓我們開始吧。

    3.如何開始一個游戲策劃?

      開始一個游戲的策劃,某種意義上說就是開始一個游戲的制作過程。首先,你必須明白一點,你的時間是有限的。如果你想成為一個專業人員,你時刻應該記住的事情是你正在制作的是一個商品,它有成本,面臨蝕本的風險。而降低成本降低風險的最有效的方法就是牢牢控制住時間,在策劃階段就要充分考慮到這點。如果你是一個業余人員的話,你也不要忘記,如果你花上兩年時間想做出一個好游戲的話,兩年后,你的游戲可能已經爛得不成了。這樣的例子真是太多了。更不用說,因為你自己的精力和時間所限,使你不得不半途而廢。所以在一開始你必須有一個計劃和目標,游戲的完成時間八個月?十二個月?十八個月?總之,你必須有一個明確的時間表,在這個時間表的基礎上,你也許不得不拋棄或改變一些想法。

      現在我們開始吧,策劃游戲需要一支筆(最好是鉛筆)、一沓草稿紙,如果需要的話還要一個計算器。

      我們重玩了很多西方的模擬建設類游戲之后,突然產生了一個想法,如果游戲中的房屋全是我們自己的民族建筑會怎么樣?那么我們就有了一個游戲的最基本的元素。

    ★在一個也許是沙漠、也許是草原的空場上建造民族特色的房屋。

      下面我們需要為玩家設置一些障礙和獎勵,這是使得游戲使人動腦筋的重要手段之一。順便說一下,并非所有的玩家都喜歡動腦筋。但你永遠不要指望一個游戲能滿足所有玩家的喜好。

      游戲的歷史背景是第二個問題。這個問題十分容易解決,我們現在所在的北京就是一個十分合適不過的地理和歷史背景了。建設古代的帝都北京。一個十分吸引人的題材不是么?我們有了游戲的第二個元素。

    ★以北京建都作為自己的歷史背景。

      建筑需要金錢、人力、物力和時間,玩家必須想方設法地得到這些必要的基礎才能獲得最后的成功。這就形成了游戲的第三個基本元素。

    ★玩家必須獲得足夠的金錢、人力、為人力所準備的糧食、建筑材料。

      下面我們組合這些基本元素使它們成為一個比較完整的體系。這個體系基本如下:將建筑進行分類,分別用于獲得金錢、人口、糧食、建筑材料等等。這些有特定功能的建筑最后分別被定義為商號、民居、糧站、官府。從商號的經營活動中,通過稅收的手段獲得金錢是一個很自然的事。而人口都是從民居中產生的也是一個不假思索的選擇。糧站的概念則完全取決于歷史背景,古代的用糧一直是依靠從南方通過水路和陸路運輸而來的,所以我們可能不用考慮種地的問題了,但由此引發了一個新的元素----道路和交通,于是我們又增加了兩個設施:道路和運河。至于官府取得建筑材料,主要是因為古代皇宮的建筑材料主要是通過官府征集而來的。

      為了表現古代北京的城市概念,我們的建筑中又加入了廟宇、戲樓等等許多附屬建筑;為了表現古代北京缺水的特點,我們又增加了水的因素和獲得水的建筑----水井。

      好像一個比較完整的游戲規則的雛形產生了。但在你想繼續之前,你必須回答以下幾個問題。

    4.其它的問題

    首先一個問題是你的場景或者稱為地圖有多大?這關系到以下問題:

    A.在有限的場景中你只能放置有限的建筑,而有限的建筑中有多少是商號呢?

    B. 用這么多的商號能用多少時間產生多少金錢?用這些金錢你能建造多少建筑呢?

    C.多少錢一個建筑,而多長時間可以把地圖上蓋滿建筑呢?

    D. 每個建筑的尺寸是多大呢?

      你可能沒有想到的是,問題答案的一部分是從你的游戲最終用戶的機器配置得來的。如果你問程序員,他會告訴你,一張640X480X256色的圖占用內存中的尺寸是300K。計算方法是640X480/1024(1K)=300。而一張同樣大小的24BIT真彩的圖在內存中則占用900K的空間,正好是300K的三倍。如果你的目標運行環境是486/16M的話,顯然DOS系統是目標操作系統。在16M內存中,除了程序的運行部分和數據部分可能占3-4M空間外,即使其它地方都被圖形占滿,你也只能放大約12個640X480X24BIT大小的圖。這意味著只有3X4個全屏的大小,太可憐了。你可以用一張1920X1920大小的圖感受一下。

      如果你用了硬盤作為虛擬內存的技術,可能會加大場景,但運行速度可能會受到相當大的影響。

      如果考慮采用256色的話,你的美術人員必須面對一個更加嚴峻的問題----調色板,這是一個令無數英雄豪杰頭痛不已的問題。它不但意味著圖形質量的嚴重下降,還意味著工作量的成倍增加。可能你沒有直接的感受,但是我們可以告訴你,過去在DOS環境下開發游戲,大約有60%的時間在解決調色板的問題。

      指望用戶在WIN95下運行游戲是你的明智選擇。這樣用戶的機器配置也會隨之提高。同時由于WIN95的虛擬內存技術和更快的硬盤驅動程序。你的場景會大大提高。但還是有著方方面面的因素在影響你的選擇。

      當場景的大小定下來后,你就可以制訂一些參數了。首先你需要合理的設計出一個城市,所謂合理是指使絕大多數玩家不感到別扭。然后,再找出其中有多少是商業建筑,多少是民居,多少是官府。在此基礎上再確定建造一個城市需要的金錢數,你需要給整個城市中的每一種建筑制訂價格。然后再與其它因素一起計算出需要的金錢,判斷一下玩家建造一個城市能忍耐的時間,太短會有不過癮的感覺,太長就會將你的游戲束之高閣。然后,你就給你的建筑制訂等級,并確定不同等級建筑的效能。總的來說,就是保證各種等級之間的平衡。比如,將計算出的金錢數分配給不同等級的商號。在分配過程中,你可以通過參數的調整引導用戶建造更多的某個等級的商號。你可以在攻略中或手冊中說明哪種等級的商號更加合算。

      象這樣使你頭暈的問題非常之多,你都要一一去面對并加以解決。你可能會說,這樣不是已經進入游戲的制作過程了嗎?其實不是,當這些參數被真正在游戲中體現出來的時候,你就會發現,有許多你沒有意想到的東西。但即便如此,你也需要在具體實施制作之前,在你的腦海中,就象真正在玩這個游戲一樣,將這個游戲的設計模擬一遍又一遍。以期從中發現問題。然后再分割成美術和程序人員都看得懂的腳本,再分別交給美術和程序人員進行制作。

      你看,游戲策劃有的時候更需要的是細致和耐心,需要對程序和美術有一定的了解。并不斷地與他人進行溝通。

    posted @ 2005-02-17 19:47 藍色雪焰 閱讀(825) | 評論 (1)編輯 收藏
     
    RPG游戲公式

    1、很久以前,在一個遙遠的地方,有一班勇者,一個魔王,一堆蝦 兵蟹將,一眾可有可無的村長和村民,以及一個感人(?)的故事。   
    2、所謂的勇者,一開始比任何生物都弱,連一只鳥或單細胞生物都打不贏。   
    3、魔王有村莊不去住,只喜歡建立一個黑暗復雜的迷宮把自己關起來,一動不動地等勇者來。
    4、魔王會派出蝦兵蟹將對付勇者,但為了使勇者能輕易應付, 會根據勇者的等級派出不太強又不太弱的對手,偶然有一些太強的對手以致勇者打不贏,便一定有另一段偶然的故事救了勇者一命。
    5、魔王令他的蝦兵蟹將將一些優質裝備,道具及錢帶在身上, 卻又不許使用,等勇者打敗他們后來自用。
    6、魔王在自己住的迷宮里四處棄置寶箱,為勇者提供足夠的裝 備和道具,以確保勇者能見到魔王。
    7、魔王傳令:如勇者逃跑,絕不能追殺,更加不能隨便行動,不得上下樓梯,不得擅自開門。 
    8、最后,魔王被勇者打敗了,有了以下結局:   

    版本一:勇者殺死魔王,世界回復和平。   
    版本二:勇者打敗魔王,王子和公主便過著快樂的生活。   
    版本三:魔王大叫:可恨啊……我會再回來的……然后消失了, (請期待下個故事)。  
    版本四:一陣強光過后,勇者和魔王一起消失了,只留下勇者的劍。   
    版本五:女主角與魔王同歸于盡或女主角犧牲的所謂永恒的悲劇。   
    版本六:這次的勇者變了魔王,下次請打他吧。   
    版本七:有個傻瓜夢醒了。   
    版本八:——THE END—— 一堆字幕   

    版本九:C:>

    posted @ 2005-02-17 19:04 藍色雪焰 閱讀(952) | 評論 (1)編輯 收藏
     

    圖塊象素化的邊緣抗鋸齒處理
    由于通常用作游戲畫面的人物肖像、背景等需要做扣圖處理的圖片在處理前底色都是白色,如果直接這樣的話,很可能做出來的象素化后的圖片(在256色的環境下,整個圖塊顏色少于40色)會有明顯的邊緣鋸齒。
    克服鋸齒或者減弱鋸齒效應以前使用的方法是修邊,即用深色,例如純黑,把圖塊的周圍描繪一遍,這樣做可以很好的減少鋸齒并能提高象素的味道,但是缺點有:對于極細的線條的處理,例如一條線只有1個象素寬,那么描邊過后就有3象素寬,這在某些場合是不可取的,很可能會破壞原由畫面的味道。而對于手機上的圖象處理,這樣的結果肯定不行。
    那么,就只好進行沒有描邊的處理方式。
    查看了日本最新格斗游戲kof2003的圖片內容,kof系列的人物從來都是不描邊的,即完全用色塊來表現角色。這對于32*32尺寸以內的小圖片來說,直接在方格內照原畫用象素畫法畫一遍就可以了,但是如果尺寸是大于32*32的或者戰局大半個手機屏幕的一幅圖呢?例如人物的特寫,rpg中會用到的頭像?這個時候如果還用象素的方式來描繪很可能會跟原畫有相當大的出入,而且修改起來也很麻煩。
    因此,可以利用原畫來進行加工!
    把底色設置為藍色(0,0,255)然后縮小圖片尺寸,這樣圖塊的周圍一圈就很好的和蘭色融合在了一起,然后通過反復的在索引色及rgb色之間的轉換減少原色,同時對一些相鄰色進行刪減(把相鄰的顏色用灌桶填成同一種顏色,例如121,34,133與121,34,144就可以合成為一個顏色)。這樣最終做出來的圖片就是以蘭色為底色的顏色很少但失真并不嚴重的圖片,把加工的圖塊扣取出來,放到背景里面,發現融合程度很好,邊緣鋸齒弱化很多。
     

    這是以白色為幕布做出來的圖片----鋸齒非常明顯。

    將完成的原畫背靜改為蘭色------是這樣樣子滴!

    將上圖減小尺寸過后------就是這個樣子滴!

    此時再用你所能想到的任何方法,在不明顯失真的前提下進行顏色壓縮,最后把蘭色去掉,就得到成品啦。哈哈啊。

    用另外一個女生的圖片做的圖
    同樣的方法,可以取得同樣良好的結果。

    反復進行不同背靜的測試,
    發現鋸齒問題解決得很好,蘭色的背靜幕布在摳圖過后能與其他顏色很好的融合。

    posted @ 2005-02-17 18:59 藍色雪焰 閱讀(1007) | 評論 (0)編輯 收藏
     

    動畫方面.

    前面的朋友說了動畫的制作,一個動畫是由很多幀組在的.,您說的方法把一幀幀的動畫做成一幅幅的圖象,再一幅幅地載入,這樣做的效率不高.應該把各幅員的動畫放在一起,做成一幅大的圖,加載一幅大圖比加載多幅小圖效率要高,一幅大圖也比多幅小圖容易管理得多.那么怎樣做呢?


    做法和前面朋友說的差不多,關鍵是這個方法:
    drawImage(Image img,dx1,dy1,dx2,dy2,sx1,sy1,sx1,sy1,this)
    其中Image img是來源圖象,
        dx1是目的區域的===左上===頂點的===X====坐標;

        dy1是目的區域的===左上===頂點的===Y====坐標;

        dx2是目的區域的===右下===頂點的===X====坐標;

        dy2是目的區域的===右下===頂點的===Y====坐標;

        sx1是來源區域的===左上===頂點的===X====坐標;

        sy1是來源區域的===左上===頂點的===Y====坐標;

        sx2是來源區域的===右下===頂點的===X====坐標;

        sy2是來源區域的===右下===頂點的===Y====坐標.
    ================================================================
    下面是一個動畫的程序代碼.
    import java.awt.*;
    import java.applet.*;
    public class Seriallmage4 extends Applet implements Runnable
    {
      int SPF;                       //用來表示線程暫停的時間
      int sx,sy;
    /******************************************************************
    sx,sy用來表示來源區域的左上頂點.因為每一幀我們都做成了長是80寬87的圖,再把它們放在一起做成一幅大的圖,
    所以來源區域的右下頂點的坐標等于左上頂點坐標加上長和高.
    式子為:sx+80;sy+87
    ******************************************************************/                     
      Image Animation;              //來源圖象
      MediaTracker MT;              //圖像追蹤器,用來檢查圖像是否完全加載
      Thread newThread;             //新的線程.
      Image OffScreen;              //后臺圖像;
      Graphics drawOffScreen;       //繪制后臺圖像的實體(實例).
    //======================init()函數===========================
      public void init()
      {
         SPF=100;                   //線程暫停的時間是100豪秒,
         MT=new MediaTracker(this); //實體化圖像追蹤器.
         Animation=getImage(getDocumentBase(),"lunisEx.gif");//加載圖像.
    //======================//追蹤圖像=============================
         MT.addImage(Animation,0);  
         try
         {
           showStatus("圖象加載中....");   //在狀態欄中顯示信息
           MT.waitForAll();                //等待加載
         }
         catch(InterruptedException E){}
    //============================================================
         OffScreen=createImage(300,300);  //建立后臺畫面的實體
         drawOffScreen=OffScreen.getGraphics();  //取得后臺畫面的繪制類
      }
    //===========================================================
      public void start()                //start函數
      {
         newThread=new Thread(this);     //建立線程
         newThread.start();              //啟動線程
      }
      public void stop()                 //stop函數
      {
         newThread=null;                 //線程設為null
      }
    //==========================================================
      public void paint(Graphics g)
      {
         drawOffScreen.setColor(Color.green);    //設置后臺畫面的背景色,
         drawOffScreen.fillRect(0,0,300,300);    //畫一次背景,作用是清除后臺畫面,如果背景是一幅畫
    //則要用這幅圖來畫.
         drawOffScreen.drawImage(Animation,20,20,250,250,sx,sy,sx+80,sy+87,this);//在后臺畫面繪制
    //的圖像,
         g.drawImage(OffScreen,0,0,300,300,this);//把后臺畫面畫到前臺畫面
      }
      public void update(Graphics g)
      {
         paint(g);                              
      }
    //====================新動畫循環在此========================50
       public void run()                       
       {
          while(newThread !=null)               //如果線程不等于null
          {
            repaint();                         // 重畫一次.
            try
            {
               Thread.sleep(SPF);              //讓線程暫停
               sx+=80;                         //畫了一次后,改變來源區域,使其到下一幀.
               if(sx==800)                     //我的圖像是一幅800*170的,有兩行,每行十幀,如果sx走到了
    //800就說明播放完第一行了,
               {
                  sy+=85;                      //播放完第一行后,改變來源區域的左上頂點使其跑到第二行.
                  sx=0;
                  if(sy==170)                  //如果播放完第二行后,又讓它回到第一行,
                  {
                     sy=0;
                  }
               }
            }
            catch(InterruptedException E){}
          }
        }

    }
    動畫每秒播放多少幀好呢?
    電影的是每秒24幀,這樣的速度不僅能產生視覺停留,而且讓人感覺到畫面非常流暢.那么是不是我們做游戲也要用這個速度呢?答案是否定的,實際上每秒播放10幀就足已產生視覺停留,如果設置高了會消耗更多的資源.至命的是,有一些手機連每秒10帖都做不到.

    posted @ 2005-02-17 18:07 藍色雪焰 閱讀(283) | 評論 (0)編輯 收藏
     

    游戲開發者的成功秘訣

    “離子風暴”開發公司的奠基人之一湯姆·霍爾先生在網上發表了一篇有趣的文章,披露了快速成為游戲開發者的“成功秘訣”。唉,世上哪有那么便宜的事呢?可湯姆·霍爾說得一本正經,一臉嚴肅,不由得你不信。再加上筆者生來對“投機取巧”“不勞而獲”“一蹴而就”之類的事情有一種按捺不住的好奇(首先聲明,筆者一生的道路走得踏踏實實,一步一個腳印,絕無此類“劣跡”,僅好奇耳),便偷偷地看了。不看則已,一看才知世上竟有湯姆·霍爾這類大滑頭,失望之余便有些忿忿然,忿忿然之余又不禁啞然失笑,豁然明白了霍爾先生的苦心。原來他出此奇文并非存心戲弄讀者,實在是有幾分“勸世”的用意在其中。
      作為聞名遐邇的游戲開發者,湯姆·霍爾在開發游戲的同時(眼下他正忙著開發一款以未來為背景的游戲:《時空異變》Anachronox),常常會收到一些來信,寫信者表露出有心在游戲業馳騁一番的雄心,提出的問題可大致劃分為以下三種:
      1、“我腦子里新奇念頭層出不窮,你愿意雇用我嗎?”
      對于第一種來信,霍爾的回答只有兩句話:“新奇念頭人人有”和“不行”。霍爾解釋說:“如今游戲業并不缺乏新奇念頭,每天都有成千上萬的新念頭在產生和消亡。盡管新奇對于一個游戲的成敗至關重要,然而更重要的是行動,將其付諸實現。愛迪生說什么來著:‘發明是百分之一的靈感和百分之九十九的汗水的混合物。’除非你拿出東西來,否則沒人愿意聽你憑空說話。埋頭制作游戲需要某種獻身精神,你可能會嘲笑那些開發游戲的人‘這也做得不好’、‘那也做得不對’,可他們實實在在完成了一件作品,這就使他們顯得與眾不同。因為99%的人是希望在他們付出勞動之前能確保得到回報的庸常之輩。”
      2、“我把自己剛剛完成的游戲設計寄來,你能抽空看看嗎?很了不起的構想噢!”
      對于這種來信,霍爾拒絕得更快,幾乎是不加思索的“No”。原因也有些出人意表:“他們寄來的構想可能是粗淺的習作,也可能是未來的驚世大作,可我是一眼也不敢看的。原因很簡單,我自己也是搞游戲策劃的,他的想法可能會與我已有的想法不謀而合。由于這種事無法證明,所以他會認為我剽竊了他的念頭。第二,我看過的東西會殘存在我的感知里,在我將來的游戲策劃中它會不知不覺地跑出來作怪,這樣我還是擺脫不了被指控剽竊的危險。第三,一旦我看過了某個構思,我就必須對其加以評論,運用我自己的想法對其進行指導。出于自私的考慮,我不想讓自己的‘酷’念頭為他人所用,我自己將來還用得著呢。還有呵,對于雜志上發表的有關游戲策劃的評說我也是避之而唯恐不及,盡管發表意見的人會說:‘這就是我認為的好游戲,你們就照著做吧。’可一旦這個念頭被某個游戲開發者所采用,并且掙了幾百萬,98%的人會打官司索要專利費。另外2%的人也會在配偶的攛掇下把你告上法庭。唯一的辦法就是眼不見為凈。”
      3、“我覺得制作游戲是一件令人興奮的事。你是怎么走上這條路的?你使用了什么工具?”
      讓霍爾先生看著順眼的大概只有這第三種問題了。由于這種信件僅僅是提出問題,而沒有隱含的危險性,霍爾先生的解答也就來得很爽快。在這里他不僅僅是回答問題,而是借此對游戲開發這個行當表述了自己獨特的見解。
      “倘若你真的喜歡電腦游戲,而你周圍又沒人會開發游戲,而你自己又沒有通過書本和工具學會開發游戲的能力,除非有人教你如何開發游戲否則你便不知該怎樣著手,倘若情況是這樣,那你就趁早打消開發游戲的念頭。
      “倘若你剛剛玩過一款偉大的游戲便覺得自己也能行,而實際上你對游戲開發一無所知,你所持有的僅僅是對成功和名望的渴望,那你就不要做游戲開發者。
      “倘若你無可救藥地迷上了電腦游戲,如醉如癡地閱讀游戲雜志,時不時地用QuickBASIC編寫自己的簡單游戲,不畏艱難地將一些好游戲的內核打開,來回比較它們的優劣,那你也不適合搞游戲開發。”
      湯姆·霍爾不留情面地把以上幾種人都“炒了魷魚”,因為在他看來,游戲業不是個僅靠熱情和癡迷便能勝任的行當。游戲公司是賺錢的公司,而不是搞游戲研究的地方。這里的競爭法則是嚴酷的,一如好萊塢電影業。在投奔這個新世界之前你要考慮再三,并且要準備在開始階段去飯館端碟子——可能會端上一輩子。
      “倘若以上所說的仍難不住你,你仍舊無法打消開發游戲的念頭,那么我就給你詳細描述一下你將面臨的艱難道路,這是一條可能走向成功的充滿荊棘的道路。”看來霍爾先生終于不忍心將那么多癡迷的年輕人拒之門外,開始布道施教了。“在你充分了解了它的艱難之后,我將向你指出另一條路,我會把不必花費多少力氣便能快速成功的超級秘訣告訴你。不過要在你閱讀了下面的內容之后。”
    打開你的電腦,在DOS狀態下敲入QBASIC,你會發現有一個程序在運行。它就是QuickBASIC,隨著DOS附送的一個編程程序。你的操作系統種沒有一樣東西是用它編寫的——它運行太慢——但你卻可以用它來寫一些游戲程序。依照霍爾先生的看法,你應該先從學習別人的程序著手。上街買一些游戲編程手冊,然后就可以干起來了。你會犯這樣或那樣的錯誤,然而沒人會在意的。在“觀摩學習”中,你要留意幾個問題:他們是如何使游戲變得有趣而使操作變得簡單的?倘若換了自己會怎么做?他們是如何改進的?什么東西使你在玩了50個小時之后依然不對它產生厭煩?
      湯姆·霍爾布置的作業可不輕松,大約需要開發100個左右自己的小游戲。前10個應該是不堪入目的入門習作,從第11個開始,應該有了一點起色,從第50個游戲起,你開始有了自己的聲音。正如音樂家的成長一樣,在你開發了100個游戲之后,游戲開發程序就成了可以讓你任意支配的樂器,用它來隨心所欲地表達自己的思想了。
      你要開發的游戲種類應該沒有限制,想到什么就開發什么。作為開始,你不妨拿游戲業最老的游戲之一 Pac-Man來作臨本,你會發現它實際上不象看上去那么簡單。你還可以讓一個小人從 A點走到 B點,躲避從 C點掉下來的一個小球。關鍵是你要開始制作游戲,并完成它。隨著自己的作品的增多,你會產生自豪感,并且會逐漸感到上癮。等到某一天,你的朋友要求拷貝一份你制作的游戲回家去玩,那時你差不多就出徒了。你再絞盡腦汁制作一個全新的游戲,一個前人從未干過的新品,盡可能把它打磨精細,然后就可以拿到游戲業界去闖運氣了。
      成功的道路不止一條,然而湯姆·霍爾走向成功的道路卻是實實在在的先例,想成為游戲開發者的人自然很想知道。我們將他走過的道路粗略地畫了一個輪廓:
      從小常去玩街機,后來自己買了個游戲機,上高中時買了電腦,自己開發了50個游戲。而后上大學學習編程,又利用業余時間制作了30個游戲,其它的業余時間則全部用來玩游戲。閱讀有關書籍與雜志的同時,開始為雜志撰稿,還為有智力障礙的學生編制游戲,畢業時拿到了計算機專業學位,在一個小公司謀得職業,它每月都有程序發布。在搞應用程序的同時又制作了幾款游戲,并開始在其它一些制作游戲的部門窺探,結識了幾個游戲天才人物,并使自己的作品得到他們的賞識。等到羽毛豐滿,便自立門戶,創建了自己的游戲公司。每天工作16個小時,每星期工作7天,掙的錢僅夠維持生計。制作各種各樣的游戲,逐步走向成功。這條道路是艱難和漫長的,然而要想成為一個游戲開發者,該做的事一樣少不了。
      “不是說還有一條道路嗎?”心有不甘的讀者問。
      不錯,的確還有一條路。走這條路你可以不必花費時間和精力去學習編程,你可以在一夜之間實現自己的夢想。湯姆·霍爾知道這條路,并把它稱為“走向成功的超級秘訣”,慷慨地在此奉獻給大家。秘訣分為三個步驟,排列如下:
      第一步:有幾百萬美金閑錢可調用。
      第二步:購買一個現成的游戲開發公司。
      第三步:別忘了在合同里加上一條,公司開發的所有游戲前面都要加上你的大名。
      大功告成!這樣你就(至少在公眾的眼里)成了一位游戲開發者。是不是簡潔而又迅速?看著游戲封面你那燙金的大名,你是否感到陶醉?唯一的不足之處就是,你別指望這樣別人會對你產生多少敬意。假如你真的想成為貨真價實的游戲開發者,除了第一條充滿荊棘的困難道路外,似乎沒有什么別的路好走。

    posted @ 2005-02-17 17:50 藍色雪焰 閱讀(245) | 評論 (0)編輯 收藏
     

    本文通過使用J2ME開發華容道游戲,介紹了J2ME游戲開發的基本模式....

    一、序言

      昨天在網上閑逛,發現一篇講解用delphi實現華容道游戲的文章,頗受啟發.于是,產生了將華容道游戲移植到手機中去的沖動.現在手機游戲琳瑯滿目,不一而足,華容道的實現版本也很多.正巧不久前筆者對J2ME下了一番功夫,正想借這個機會小試牛刀。選用J2ME的原因還有一個就是目前Java開發大行其到,無線增殖業務迅猛發展,J2ME的應用日漸活躍起來,也希望我的這篇文章能夠為J2ME知識的普及和開發團隊的壯大推波助瀾。由于長期受ISO規范的影響,這次小試牛刀我也打算遵照軟件工程的要求,并采取瀑布式的開發模式來規劃項目,也希望借此機會向各位沒有機會參與正式項目開發的讀者介紹一下軟件開發的流程。

      這里我們先定義項目組的人員體制(其實只有我一個人):技術調研、需求分析、概要設計、詳細設計、編碼、測試均有筆者一人擔任;美工這里我找了個捷徑,盜用網上現成的圖片,然后用ACDSee把它由BMP轉換成PNG格式(我出于講座的目的,未做商業應用,應該不算侵權吧);至于發布工作,由于缺少OTA服務器,此項工作不做(但是我會介紹這步如何做)。

      接下來,我們規劃一下項目實現的時間表,以我個人經驗,設想如下:技術調研用2天(這部分解決項目的可行性和重大技術問題,時間會長一些),需求分析用半天(畢竟有現成的東東可以參照,只要理清思路就行了,況且還有很多以前用過的設計模式和寫好的代碼),概要設計再用半天(有了需求,概要只不夠是照方抓藥),詳細設計要用2天(這一步要把所有的問題想清楚,還要盡可能的準確描述出來),編碼用2天(其實1天就夠了,技術已經不是問題,多計劃出一天來應付突發事件),測試用2天(測試應該至少占全部項目的四分之一,不過這個項目只是一個Demo,也太簡單了),發布也要用上半天(盡管我們不去實際發布它,但是還要花點時間搞清楚應該如何做),最后就是項目總結和開慶功會(時間待定)。

      二.利其器

      “公欲善其事,必先利其器”,做項目之前第一步是前期調研.我們要做的華容道這個東東隨處可見,我們要調研的是兩個方面:

      1、游戲的內容:游戲本身很簡單,就是有幾個格子,曹操占據其中一個較大的格子,然后被幾個格子包圍,這些格子形狀不一定相同,但是擋住了曹操移動的方向.游戲者需要挪動這些格子最終把曹操移動到一個指定的位置才算是過關.更具體的分析我們放在后面需求分析和概要設計中討論。

      2、技術儲備:談到技術,這里簡單介紹一下J2ME.Java有三個版本,分別是J2ME(微型版).J2SE(標準版).J2EE(企業版).J2ME是一個標準,采用3層結構設計.最低層是配置層(Configuration)也就是設備層,其上是簡表層(Profile),再上是應用層(Application).MIDP就是移動信息設備簡表,目前主流手機支持MIDP1.0,最新的是MIDP2.0,它比前一個版本增加了對游戲的支持,在javax.microedition.lcdui.game包中提供了一些類來處理游戲中的技術,比如我們后面會用到的Sprite類,它是用來翻轉圖片的.權衡再三,筆者決定使用MIDP2.0來做開發.首先需要安裝一個J2ME的模擬器,我們就用Sun公司的WTK2.0,我覺得Sun的東西最權威.當然你也可以使用Nokia.Siemens或是Motolora等其他模擬器,但是他們的JDK不盡相同,寫出來的程序移植是比較麻煩的.Sun公司的WTK2.0可以到<A href="http://here/下">http://here/下</A>載,當然要想成功下載的前提是你要先注冊成為Sun的會員(其實這樣對你是有好處的).當下來之后就是按照提示一步一步的安裝.安裝好了之后,我們用一個"Hello World"程序開始你的J2ME之旅.我們啟動WTK2.0工具集中的KToolBar,然后點擊New Project按鈕,在彈出的輸入框中輸入Project Name為HelloWorld,MIDlet Class Name為Hello,然后點擊Create Project,開始生成項目,工具會彈出MIDP配置簡表,這里接受生成的默認值(以后還可以修改)點擊OK,工具提示我們把寫好的Java源程序放到[WTK_HOME]\apps\HelloWorld\src目錄之下.我們編輯如下代碼,并保存在上述目錄之下,文件名為Hello.java。

    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    public class Hello extends MIDlet
    {
    private Display display;
    public Hello(){
    display =Display.getDisplay(this);
    }
    public void startApp(){
    TextBox t = new TextBox("Hello","Hello",256,0);
    display.setCurrent(t);
    }
    public void pauseApp(){
    }
    public void destroyApp(boolean unconditional){
    }
    }


      保存好了之后,點擊Build按鈕,工具會為你編譯程序,如無意外再點擊Run按鈕,會彈出一個手機界面,剩下的就不用我教了吧(用鼠標對手機按鍵一頓狂點)。呵呵,你的第一個J2ME程序已經OK了.什么?你還一點都沒懂呢(真是厲害,不懂都能寫出J2ME程序來,果然是高手).我這里主要是介紹WTK2.0工具的使用,程序并不是目的,不懂的話后面還會有詳細的解說,這里只是帶你上路.什么?你不懂Java!那也沒有關系,后面我再講得細一點。

      跳過J2ME,我們先來講點游戲的理論.具體到華容道這個游戲,主要有三個方面,貼圖.游戲操作.邏輯判斷.這里講講貼圖,其他兩方面放在概要設計和詳細設計里講.所謂的貼圖,其實就是畫圖,就是在要顯示圖形的位置上輸出一副圖片,(要是牽扯到動畫就要麻煩一些,可以使用TimerTask.Thread或Rannable之類的技術),這副圖片可以是事先準備好的也可以是臨時處理的.在J2ME中有一個Image類,專門用于管理圖片,它有createImage()方法,可以直接讀取圖片文件(J2ME只支持PNG格式的圖片),也可以截取已有的圖片的一部分(這樣我們可以把很多圖片放在一起,然后一張一張的截下來,好處是節省存儲空間和文件讀取時間,對于手機這兩者都是性能的瓶頸).J2ME還有一個Graphics類,專門用于繪圖,它有drawImage()方法,可以把一副圖片在指定的位置上顯示出來,它還有drawRect()方法和setColor()方法,這兩個方法在后面我們進行游戲操作時就會用到,這里先交代一下.有了圖片和繪圖的方法,還需要知道把圖畫到誰身上,J2ME提供了一個Canvas類,字面意思就是畫布,它有一個paint()方法用于刷新頁面,還有一個repaint()方法用于調用paint()方法.聽著有些糊涂是吧,不要緊,我來結合具體程序講解一下.為了今后編程的方便,我們創建兩個類Images和Draw,Images用于保存一些常量值和圖片,Draw主要是用于畫圖,這兩個類的源代碼如下。

      Images類的源代碼如下:

    package huarongroad;

    import javax.microedition.lcdui.*;
    import javax.microedition.lcdui.game.*;

    public class Images {//保存常量
    //繪圖位置常量
    public static final int UNIT = 32;//方塊的單位長度
    public static final int LEFT = 10;//畫圖的左邊界頂點
    public static final int TOP = 9;//畫圖的上邊界頂點
    //地圖位置常量
    public static final int WIDTH = 4;//地圖的寬度
    public static final int HEIGHT = 5;//地圖的高度
    //地圖標記常量
    public static final byte CAOCAO = (byte) 'a'; <A href="file://曹">file://曹</A>操的地圖標記
    public static final byte MACHAO = (byte) 'b';//馬超的地圖標記
    public static final byte HUANGZHONG = (byte) 'c';//黃忠的地圖標記
    public static final byte GUANYU = (byte) 'd';//關羽的地圖標記
    public static final byte ZHANGFEI = (byte) 'e';//張飛的地圖標記
    public static final byte ZHAOYUN = (byte) 'f';//趙云的地圖標記
    public static final byte ZU = (byte) 'g';//卒的地圖標記
    public static final byte BLANK = (byte) 'h';//空白的地圖標記
    public static final byte CURSOR = (byte) 'i';//光標的地圖標記
    //地圖組合標記常量
    public static final byte DLEFT = (byte) '1'; <A href="file://組">file://組</A>合圖形左邊標記
    public static final byte DUP = (byte) '2'; <A href="file://組">file://組</A>合圖形上邊標記
    public static final byte DLEFTUP = (byte) '3'; <A href="file://組">file://組</A>合圖形左上標記
    //圖片常量
    public static Image image_base;//基本圖片
    public static Image image_Zhaoyun;//趙云的圖片
    public static Image image_Caocao;//曹操的圖片
    public static Image image_Huangzhong;//黃忠的圖片
    public static Image image_Machao;//馬超的圖片
    public static Image image_Guanyu;//關羽的圖片
    public static Image image_Zhangfei;//張飛的圖片
    public static Image image_Zu;//卒的圖片
    public static Image image_Blank;//空白的圖片
    public static Image image_Frame;//游戲框架的圖片

    public Images() {//構造函數
    }

    public static boolean init() {//初始化游戲中用到的圖片
    try {
    image_base = Image.createImage("/huarongroad/BITBACK.png");
    image_Frame = Image.createImage(image_base, 126, 0, 145, 177,
    Sprite.TRANS_NONE);
    //Sprite類是用來翻轉圖片的,是MIDP2.0新新增加的支持游戲的特性
    image_Zhaoyun = Image.createImage(image_base, 0, 0, UNIT, 2 * UNIT,
    Sprite.TRANS_NONE);
    image_Caocao = Image.createImage(image_base, UNIT, 0, 2 * UNIT,
    2 * UNIT, Sprite.TRANS_NONE);
    image_Huangzhong = Image.createImage(image_base, 3 * UNIT, 0, UNIT,
    2 * UNIT,
    Sprite.TRANS_NONE);
    image_Machao = Image.createImage(image_base, 0, 2 * UNIT, UNIT,
    2 * UNIT,
    Sprite.TRANS_NONE);
    image_Guanyu = Image.createImage(image_base, UNIT, 2 * UNIT,
    2 * UNIT, UNIT,
    Sprite.TRANS_NONE);
    image_Zhangfei = Image.createImage(image_base, 3 * UNIT, 2 * UNIT,
    UNIT, 2 * UNIT,
    Sprite.TRANS_NONE);
    image_Zu = Image.createImage(image_base, 0, 4 * UNIT, UNIT, UNIT,
    Sprite.TRANS_NONE);
    image_Blank = Image.createImage(image_base, 1 * UNIT, 4 * UNIT,UNIT,
    UNIT,
    Sprite.TRANS_NONE);

    return true;
    }catch (Exception ex) {
    return false;
    }
    }
    }

      Draw類的源代碼如下:

    package huarongroad;

    import javax.microedition.lcdui.*;

    public class Draw {
    //繪制游戲中的圖片
    public Draw(Canvas canvas) {//構造函數
    }

    public static boolean paint(Graphics g, byte img, int x, int y) {
    //在地圖的x,y點繪制img指定的圖片
    try {
    paint(g, img, x, y, Images.UNIT);//把地圖x,y點轉化成畫布的絕對坐標,繪圖
    return true;
    }
    catch (Exception ex) {
    return false;
    }
    }

    public static boolean paint(Graphics g, byte img, int x, int y, int unit) {
    try {
    switch (img) {
    case Images.CAOCAO://畫曹操
    //變成絕對坐標,并做調整
    g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.GUANYU://畫關羽
    g.drawImage(Images.image_Guanyu, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.HUANGZHONG://畫黃忠
    g.drawImage(Images.image_Huangzhong, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.MACHAO://畫馬超
    g.drawImage(Images.image_Machao, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.ZHANGFEI://畫張飛
    g.drawImage(Images.image_Zhangfei, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.ZHAOYUN://畫趙云
    g.drawImage(Images.image_Zhaoyun, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.ZU://畫卒
    g.drawImage(Images.image_Zu, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.BLANK://畫空白
    g.drawImage(Images.image_Blank, Images.LEFT + x * unit,
    Images.TOP + y * unit,
    Graphics.TOP | Graphics.LEFT);
    break;
    case Images.CURSOR://畫光標
    g.drawRect(Images.LEFT + x * unit,
    Images.TOP + y * unit,Images.UNIT,Images.UNIT);
    break;
    }
    return true;
    }catch (Exception ex) {
    return false;
    }
    }
    }

      其中Images類存的是繪圖位置常量(也就是在畫圖時每個格子的長度和相對坐標原點位置要進行的調整)、地圖位置常量(地圖的長、寬),地圖標記常量(人物對應的記號),地圖組合標記常量(后面會細說),圖片常量(存放人物的圖片);Draw類主要負責在制定的位置畫出人物圖片。下面我來說說Images類中的地圖標記常量和地圖組合標記常量。為了能夠靈活的安排各個關面的布局,我們決定把游戲布局的信息存儲在外部文件中,然后程序啟動后把它讀進來。這樣我們制定了一套存儲圖片的代碼,這就是地圖標記常量,如上面Images類中定義的Caocao(曹操)用a字符來表示,當程序讀到a字符時就能將它轉化成曹操對應的圖片,并在讀到a字符的位置上進行顯示。但是從實際觀察中我們發現所有的圖片并不是統一大小的,有的占4個格子,有的占2個格子,還有的占1個格子,而且即便同是占兩個格子的圖片還有橫、豎之分。有鑒于此,我們引入了地圖組合標記常量,就是說在遇到占有多個格子的時候,值1(也就是Images.LEFT)表示它的左邊是一個真正的地圖標記,值2(也就是Images.UP)表示它的上邊是一個真正的地圖標記,值1(也就是Images.LEFTUP)表示它的左上邊是一個真正的地圖標記。地圖組合標記常量其實就是用來占位置的,與實際顯示無關,當后面我們將到移動時還會再來分析組合標記的使用。

      Draw類主要是用來在畫布上畫出圖形,它有兩個paint方法,這是很常見的函數重載。但是程序中實際上只用到了4個參數的paint方法,它直接獲得要畫圖片的相對坐標位置信息,然后調用5個參數的paint方法。5個參數的paint方法將相對坐標位置信息轉換成絕對位置,并實際調用Graphics.drawImage()方法,將Images中的圖片畫了出來。這種實現方法的好處是靈活和便于擴展,但你需要畫圖的位置并不能夠對應到格子中的相對坐標位置時,你就可以直接調用5個參數的paint方法,而不必再去修改這各類;但你添加新的圖片時,只要在Images中增加對應的常量,然后向Draw中5個參數的paint方法添加一條處理就可以了。
    寫到這里,兩天的時間剛好用完。

      三、需求分析

      這部分叫做需求分析,聽起來挺嚇人的,其實就是搞清楚我們要做什么,做成什么樣,那些不做。下面我引領著大家共同來完成這一步驟。首先,我們要做一個華容道的游戲,華容道的故事這里不再贅述了,但其中的人物在這里限定一下,如上面Images類里的定義,我們這個版本只提供曹操(Caocao)、關羽(Guanyu)、張飛(Zhangfei)、趙云(Zhaoyun)、黃忠(Huangzhong)、馬超(Machao)和卒(Zu)。我們這里也限定一下游戲的操作方法:首先要通過方向鍵選擇一個要移動的區域(就是一張圖片),被選擇的區域用黑色方框框住;選好后按Fire鍵(就是確定鍵)將這塊區域選中,被選中的區域用綠色方框框住;然后選擇要移動到的區域,此時用紅色方框框住被選擇的區域;選好要移動到的區域之后按Fire鍵將要移動的區域(圖片)移到要移動到的區域,并去掉綠色和紅色的方框。這里需要強調的概念有選擇的區域、選中的區域、要移動的區域和要移動到的區域,這四個概念請讀者注意區分,當然也應當把這一部分記入數據字典之中。為了使文章的重點突出(介紹如何制作一個J2ME的收集游戲),我們這里限定一些與本主題無關的內容暫不去實現:過關之后的動畫(實現時要用到TimerTask或Thread類,后續的系列文章中我會詳細介紹動畫方面的知識)、關面之間的切換(其實很簡單,當完成任務之后重新再做一邊)、暫停和保存等操作(這部分的內容介紹的資料很多,我也寫不出什么新的東東來,難免抄襲,故此免掉)。

      需求分析基本完成,離下午還有一段時間,馬上動手用ACDSee把從網上找來的BMP文件,調整其大小為271*177(我的這個圖片是兩個部分合在一起,所以比手機實際屏幕大了),另存為PNG格式。半天時間剛剛好,不但搞清楚了要做的東東,還把要用的圖片準備好了。

      四、概要設計

      概要設計是從需求分析過渡到詳細設計的橋梁和紐帶,這一部分中我們確定項目的實現方法和模塊的劃分。我們決定將整個項目分成五個部分,分別是前面介紹的Images、Draw,還有Map和Displayable1和MIDlet1。Images和Draw類功能簡單、結構固定,因此很多項目我們都使用這兩各類,這里直接拿來改改就能用了,前面已經介紹過這里不再贅述。Map類是用來從外部文件讀入地圖,然后保存在一個數組之中,這部分的內容是我們在本階段討論的重點。Displayable1是一個繼承了Canvas類的畫布,它用來處理程序的主要控制邏輯和一部分控制邏輯所需的輔助函數,主要函數應該包括用來繪圖的paint()函數、用來控制操作的keyPressed()函數、用來控制選擇區域的setRange()函數、用來控制選擇要移動到區域的setMoveRange()函數、用來移動選中區域的Move()函數和判斷是否完成任務的win()函數,更具體的分析,我們放到詳細設計中去細化。MIDlet1實際上就是一個控制整個J2ME應用的控制程序,其實也沒有什么可特別的,它和我們前面介紹的"Hello World"程序大同小異,這里就不展開來說了,后面會貼出它的全部代碼。

      Map類主要應該有一個Grid[][]的二維數組,用來存放華容道的地圖,還應該有一個read_map()函數用來從外部文件讀取地圖內容填充Grid數據結構,再就是要有一個draw_map()函數用來把Grid數據結構中的地圖內容轉換成圖片顯示出來(當然要調用Draw類的paint方法)。說到讀取外部文件,筆者知道有兩種方法:一種是傳統的定義一個InputStream對象,然后用getClass().getResourceAsStream()方法取得輸入流,然后再從輸入流中取得外部文件的內容,例如

    InputStream is = getClass().getResourceAsStream("/filename");
    if (is != null) {
    byte a = (byte) is.read();
    }

      這里請注意文件名中的根路徑是相對于便以后的class文件放置的位置,而不是源文件(java)。第二種方法是使用onnector.openInputStream方法,然后打開的協議是Resource,但是這種方法筆者反復嘗試都沒能調通,報告的錯誤是缺少Resource協議,估計第二種方法用到J2ME的某些擴展類包,此處不再深究。由于以前已經做過一些類似華容道這樣的地圖,這里直接給出Map類的代碼,后面就不再詳細解釋Map類了,以便于我們可以集中精力處理Displayable1中的邏輯。Map類的代碼如下:

    package huarongroad;

    import java.io.InputStream;
    import javax.microedition.lcdui.*;

    public class Map {
    //處理游戲的地圖,負責從外部文件加載地圖數據,存放地圖數據,并按照地圖數據繪制地圖

    public byte Grid[][];//存放地圖數據

    public Map() {//構造函數,負責初始化地圖數據的存儲結構
    this.Grid = new byte[Images.HEIGHT][Images.WIDTH];
    //用二維數組存放地圖數據,注意第一維是豎直坐標,第二維是水平坐標
    }

    public int[] read_map(int i) {
    <A href="file://從">file://從</A>外部文件加載地圖數據,并存放在存儲結構中,返回值是光標點的位置
    //參數是加載地圖文件的等級
    int[] a = new int[2];//光標點的位置,0是水平位置,1是豎直位置
    try {
    InputStream is = getClass().getResourceAsStream(
    "/huarongroad/level".concat(String.valueOf(i)));
    if (is != null) {
    for (int k = 0; k < Images.HEIGHT; k++) {
    for (int j = 0; j < Images.WIDTH; j++) {
    this.Grid[k][j] = (byte) is.read();
    if ( this.Grid[k][j] == Images.CURSOR ) {
    //判斷出光標所在位置
    a[0] = j;//光標水平位置
    a[1] = k;//光標豎直位置
    this.Grid[k][j] = Images.BLANK;//將光標位置設成空白背景
    }
    }
    is.read();//讀取回車(13),忽略掉
    is.read();//讀取換行(10),忽略掉
    }
    is.close();
    }else {
    //讀取文件失敗
    a[0] = -1;
    a[1] = -1;
    }
    }catch (Exception ex) {
    //打開文件失敗
    a[0] = -1;
    a[1] = -1;
    }
    return a;
    }

    public boolean draw_map(Graphics g) {
    //調用Draw類的靜態方法,繪制地圖
    try {
    for (int i = 0; i < Images.HEIGHT; i++) {
    for (int j = 0; j < Images.WIDTH; j++) {
    Draw.paint(g, this.Grid[i][j], j, i);//繪制地圖
    }
    }
    return true;
    }catch (Exception ex) {
    return false;
    }
    }
    }

      對于像華容道這樣的小型地圖可以直接用手工來繪制地圖的內容,比如:

    fa1c
    2232
    bd1e
    2gg2
    gihg

      但是,如果遇到像坦克大戰或超級瑪莉那樣的地圖,就必須另外開發一個地圖編輯器了(我會在后續的文章中介紹用vb來開發一個地圖編輯器)。

      五、詳細設計

      詳細設計是程序開發過程中至關重要的一個環節,好在我們在前面的各個階段中已經搭建好了項目所需的一些工具,現在這個階段中我們只需集中精力設計好Displayable1中的邏輯。(兩天的時間當然不只干這點活,還要把其他幾個類的設計修改一下)

      Displayable1這個類負責處理程序的控制邏輯。首先,它需要有表示當前關面的變量level、表示當前光標位置的變量loc、表示要移動區域的變量SelectArea、表示要移動到的區域的變量MoveArea、表示是否已有區域被選中而準備移動的變量Selected和Map類的實例MyMap。然后,我們根據用戶按不同的鍵來處理不同的消息,我們要實現keyPressed()函數,在函數中我們處理按鍵的上下左右和選中(Fire),這里的處理需要我展開來講一講,后面我很快會把這一部分詳細展開。

      接下來,是實現paint()函數,我們打算在這一部分中反復的重畫背景、地圖和選擇區域,這個函數必須處理好區域被選中之后的畫筆顏色的切換,具體講就是在沒有選中任何區域時要用黑色畫筆,當選重要移動的區域時使用綠色畫筆,當選擇要移動到的區域時改用紅色畫筆(當然附加一張流程圖是必不可少的)。

      再下面要實現的setRange()函數和setMoveRange()函數,這兩個函數用來設置要移動的區域和要移動到的區域,我的思路就是利用前面在Images類中介紹過的地圖組合標記常量,當移動到地圖組合標記常量時,根據該點地圖中的值做逆向變換找到相應的地圖標記常量,然后設置相應的loc、SelectArea和MoveArea,其中setMoveRange()函數還用到了一個輔助函數isInRange(),isInRange()函數是用來判斷給定的點是否在已選中的要移動的區域之內,如果isInRange()的返回值是假并且該點處的值不是空白就表明要移動到的區域侵犯了其他以被占用的區域。有了setRange()和setMoveRange()函數,Move()函數就水到渠成了,Move()函數將要移動的區域移動到要移動到的區域,在移動過程中分為三步進行:

      第一.復制要移動的區域;

      第二.將復制出的要移動區域復制到要移動到的區域(這兩步分開進行的目的是防止在復制過程中覆蓋掉要移動的區域);

      第三.用isInRange2()判斷給定的點是否在要移動到的區域內,將不在要移動到的區域內的點設置成空白。

      下面我們詳細的分析一下keyPressed()函數的實現方法:首先,keyPressed()函數要處理按鍵的上下左右和選中(Fire),在處理時需要用Canvas類的getGameAction函數來將按鍵的鍵值轉換成游戲的方向,這樣可以提高游戲的兼容性(因為不同的J2ME實現,其方向鍵的鍵值不一定是相同的)。

      接下來,分別處理四個方向和選中.當按下向上時,先判斷是否已經選定了要移動的區域(即this.selected是否為真),如果沒有選中要移動區域則讓光標向上移動一格,然后調用setRange()函數設置選擇要移動的區域,再調用repaint()函數刷新屏幕,否則如果已經選中了要移動的區域,就讓光標向上移動一格,然后調用setMoveRange()函數判斷是否能夠向上移動已選中的區域,如果能移動就調用repaint()函數刷新屏幕,如果不能移動就讓光標向下退回到原來的位置。

      當按下向下時,先判斷是否已經選定了要移動的區域,如果沒有選中要移動的區域則判斷當前所處的區域是否為兩個格高,如果是兩個格高則向下移動兩格,如果是一個格高則向下移動一格,接著再調用setRange()函數設置選擇要移動的區域,而后調用repaint()函數刷新屏幕,否則如果已經選中了要移動的區域,就讓光標向下移動一格,然后調用setMoveRange()函數判斷是否能夠向下移動已選中的區域,如果能移動就調用repaint()函數刷新屏幕,如果不能移動就讓光標向上退回到原來的位置.按下向左時情況完全類似向上的情況,按下向右時情況完全類似向下的情況,因此這里不再贅述,詳細情況請參見程序的源代碼。

      當按下選中鍵時,先判斷是否已經選中了要移動的區域,如果已經選中了要移動的區域就調用Move()函數完成由要移動的區域到要移動到的區域的移動過程,接著調用repaint()函數刷新屏幕,然后將已選擇標記置成false,繼續調用win()函數判斷是否完成了任務,否則如果還沒有選定要移動的區域則再判斷當前選中區域是否為空白,如果不是空白就將選中標記置成true,然后刷新屏幕.這里介紹一個技巧,在開發程序遇到復雜的邏輯的時候,可以構造一格打印函數來將所關心的數據結構打印出來以利調試,這里我們就構造一個PrintGrid()函數,這個函數純粹是為了調試之用,效果這得不錯.至此我們完成了編碼前的全部工作。

      六.編碼

      整個項目共有五個類,有四個類的代碼前面已經介紹過了,而且是在其他項目中使用過的相對成熟的代碼.現在只需全力去實現Displayable1類.Displayable1類的代碼如下:

    package huarongroad;

    import javax.microedition.lcdui.*;

    public class Displayable1 extends Canvas implements CommandListener {

    private int[] loc = new int[2]; <A href="file://光">file://光</A>標的當前位置,0是水平位置,1是豎直位置
    private int[] SelectArea = new int[4];//被選定的區域,即要移動的區域
    private int[] MoveArea = new int[4];//要移動到的區域
    private Map MyMap = new Map();//地圖類
    private boolean selected;//是否已經選中要移動區域的標志
    private int level;//但前的關面
    public Displayable1() {//構造函數
    try {
    jbInit();//JBuilder定義的初始化函數
    }catch (Exception e) {
    e.printStackTrace();
    }
    }
    private void Init_game(){
    //初始化游戲,讀取地圖,設置選擇區域,清空要移動到的區域
    this.loc = MyMap.read_map(this.level);//讀取地圖文件,并返回光標的初始位置
    //0為水平位置,1為豎直位置
    this.SelectArea[0] = this.loc[0];//初始化選中的區域
    this.SelectArea[1] = this.loc[1];
    this.SelectArea[2] = 1;
    this.SelectArea[3] = 1;
    this.MoveArea[0] = -1;//初始化要移動到的區域
    this.MoveArea[1] = -1;
    this.MoveArea[2] = 0;
    this.MoveArea[3] = 0;
    }
    private void jbInit() throws Exception {//JBuilder定義的初始化函數
    <A href="file://初">file://初</A>始化實例變量
    this.selected = false;//設置沒有被選中的要移動區域
    this.level = 1;
    Images.init();//初始化圖片常量
    Init_game();//初始化游戲,讀取地圖,設置選擇區域,清空要移動到的區域
    setCommandListener(this);//添加命令監聽,這是Displayable的實例方法
    addCommand(new Command("Exit", Command.EXIT, 1));//添加“退出”按鈕
    }

    public void commandAction(Command command, Displayable displayable) {
    //命令處理函數
    if (command.getCommandType() == Command.EXIT) {//處理“退出”
    MIDlet1.quitApp();
    }
    }

    protected void paint(Graphics g) {
    //畫圖函數,用于繪制用戶畫面,即顯示圖片,勾畫選中區域和要移動到的區域
    try {
    g.drawImage(Images.image_Frame, 0, 0,
    Graphics.TOP | Graphics.LEFT);//畫背景
    MyMap.draw_map(g);//按照地圖內容畫圖
    if ( this.selected )
    g.setColor(0,255,0);//如果被選中,改用綠色畫出被選中的區域
    g.drawRect(this.SelectArea[0] * Images.UNIT + Images.LEFT,
    this.SelectArea[1] * Images.UNIT + Images.TOP,
    this.SelectArea[2] * Images.UNIT,
    this.SelectArea[3] * Images.UNIT);//畫出選擇區域,
    <A href="file://如">file://如</A>果被選中,就用綠色
    <A href="file://否">file://否</A>則,使用黑色
    g.setColor(255,255,255);//恢復畫筆顏色
    if (this.selected) {//已經選中了要移動的區域
    g.setColor(255, 0, 255);//改用紅色
    g.drawRect(this.MoveArea[0] * Images.UNIT + Images.LEFT,
    this.MoveArea[1] * Images.UNIT + Images.TOP,
    this.MoveArea[2] * Images.UNIT,
    this.MoveArea[3] * Images.UNIT);//畫出要移動到的區域
    g.setColor(255, 255, 255);//恢復畫筆顏色
    }
    }catch (Exception ex) {
    }
    System.out.println(Runtime.getRuntime().freeMemory());
    System.out.println(Runtime.getRuntime().totalMemory());
    }

    private void setRange() {
    //設置移動后能夠選中的區域
    //調整當前光標位置到地圖的主位置,即記錄人物信息的位置
    if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {
    this.loc[0] -= 1;//向左調
    }else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {
    this.loc[1] -= 1;//向上調
    }else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {
    this.loc[0] -= 1;//向左調
    this.loc[1] -= 1;//向上調
    }
    this.SelectArea[0] = this.loc[0];//設置光標的水平位置
    this.SelectArea[1] = this.loc[1];//設置光標的豎直位置
    //設置光標的寬度
    if (this.loc[0] + 1 < Images.WIDTH) {
    this.SelectArea[2] = this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] != (byte) '1' ?
    1 : 2;
    }else {
    this.SelectArea[2] = 1;
    }
    //設置光標的高度
    if (this.loc[1] + 1 < Images.HEIGHT) {
    this.SelectArea[3] = this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] != (byte) '2' ?
    1 : 2;
    }else {
    this.SelectArea[3] = 1;
    }
    }

    private boolean setMoveRange() {
    //設置要移動到的區域,能夠移動返回true,否則返回false
    for (int i = 0; i < this.SelectArea[2]; i++) {
    for (int j = 0; j < this.SelectArea[3]; j++) {
    if (this.loc[1] + j >= Images.HEIGHT ||
    this.loc[0] + i >= Images.WIDTH ||
    (!isInRange(this.loc[0] + i, this.loc[1] + j) &&
    this.MyMap.Grid[this.loc[1] + j][this.loc[0] + i] !=
    Images.BLANK)) {
    return false;
    }
    }
    }
    this.MoveArea[0] = this.loc[0];
    this.MoveArea[1] = this.loc[1];
    this.MoveArea[2] = this.SelectArea[2];
    this.MoveArea[3] = this.SelectArea[3];
    return true;
    }

    private boolean isInRange(int x, int y) {
    //判斷給定的(x,y)點是否在選定區域之內,x是水平坐標,y是豎直坐標
    if (x >= this.SelectArea[0] &&
    x < this.SelectArea[0] + this.SelectArea[2] &&
    y >= this.SelectArea[1] &&
    y < this.SelectArea[1] + this.SelectArea[3]) {
    return true;
    }else {
    return false;
    }
    }

    private boolean isInRange2(int x, int y) {
    //判斷給定的(x,y)點是否在要移動到的區域之內,x是水平坐標,y是豎直坐標
    if (x >= this.MoveArea[0] &&
    x < this.MoveArea[0] + this.MoveArea[2] &&
    y >= this.MoveArea[1] &&
    y < this.MoveArea[1] + this.MoveArea[3]) {
    return true;
    }else {
    return false;
    }
    }

    protected void keyPressed(int keyCode) {
    //處理按下鍵盤的事件,這是Canvas的實例方法
    switch (getGameAction(keyCode)) {//將按鍵的值轉化成方向常量
    case Canvas.UP://向上
    if (!this.selected) {//還沒有選定要移動的區域
    if (this.loc[1] - 1 >= 0) {//向上還有移動空間
    this.loc[1]--;//向上移動一下
    setRange();//設置光標移動的區域,該函數能將光標移動到地圖主位置
    repaint();//重新繪圖
    }
    }else {//已經選定了要移動的區域
    if (this.loc[1] - 1 >= 0) {//向上還有移動空間
    this.loc[1]--;//向上移動一下
    if (setMoveRange()) {//能夠移動,該函數能夠設置要移動到的區域
    repaint();//重新繪圖
    }else {//不能移動
    this.loc[1]++;//退回來
    }
    }
    }
    break;
    case Canvas.DOWN://向下
    if (!this.selected) {//還沒有選定要移動的區域
    if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有移動空間
    if (this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] ==
    Images.DUP){//該圖片有兩個格高
    this.loc[1]++;//向下移動一下
    if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有
    <A href="file://移">file://移</A>動空間
    this.loc[1]++;//向下移動一下
    setRange();//設置光標移動的區域,
    <A href="file://該">file://該</A>函數能將光標移動到地圖主位置
    repaint();//重新繪圖
    }else {//向下沒有移動空間
    this.loc[1]--;//退回來
    }
    }else {//該圖片只有一個格高
    this.loc[1]++;//向下移動一下
    setRange();//設置光標移動的區域,
    <A href="file://該">file://該</A>函數能將光標移動到地圖主位置
    repaint();//重新繪圖
    }
    }else {
    }
    }else {//已經選定了要移動的區域
    if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有移動空間
    this.loc[1]++;//向下移動一下
    if (setMoveRange()) {//能夠移動,該函數能夠設置要移動到的區域
    repaint();//重新繪圖
    }else {//不能移動
    this.loc[1]--;//退回來
    }
    }
    }
    break;
    case Canvas.LEFT://向左
    if (!this.selected) {//還沒有選定要移動的區域
    if (this.loc[0] - 1 >= 0) {//向左還有移動空間
    this.loc[0]--;//向左移動一下
    setRange();//設置光標移動的區域,該函數能將光標移動到地圖主位置
    repaint();//重新繪圖
    }
    }else {//已經選定了要移動的區域
    if (this.loc[0] - 1 >= 0) {//向左還有移動空間
    this.loc[0]--;//向左移動一下
    if (setMoveRange()) {//能夠移動,該函數能夠設置要移動到的區域
    repaint();//重新繪圖
    }else {//不能移動
    this.loc[0]++;//退回來
    }
    }
    }
    break;
    case Canvas.RIGHT://向右
    if (!this.selected) {//還沒有選定要移動的區域
    if (this.loc[0] + 1 < Images.WIDTH) {//向右還有移動空間
    if (this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] ==
    Images.DLEFT) {//該圖片有兩個格寬
    this.loc[0]++;//向右移動一下
    if (this.loc[0] + 1 < Images.WIDTH) {//向右還有
    <A href="file://移">file://移</A>動空間
    this.loc[0]++;//向右移動一下
    setRange();//設置光標移動的區域,
    <A href="file://該">file://該</A>函數能將光標移動到地圖主位置
    repaint();//重新繪圖
    }else {//向右沒有移動空間
    this.loc[0]--;//退回來
    }
    }else {//該圖片只有一個格寬
    this.loc[0]++;//向右移動一下
    setRange();//設置光標移動的區域,
    <A href="file://該">file://該</A>函數能將光標移動到地圖主位置
    repaint();//重新繪圖
    }
    }else {
    }
    }else {//已經選定了要移動的區域
    if (this.loc[0] + 1 < Images.WIDTH) {//向右還有移動空間
    this.loc[0]++;//向右移動一下
    if (setMoveRange()) {//能夠移動,該函數能夠設置要移動到的區域
    repaint();//重新繪圖
    }else {//不能移動
    this.loc[0]--;//退回來
    }
    }
    }
    break;
    case Canvas.FIRE:
    if (this.selected) {//已經選定了要移動的區域
    Move();//將要移動的區域移動到剛選中的區域
    repaint();//重新繪圖
    this.selected = false;//清除已選定要移動區域的標志
    if ( win()) {
    System.out.println("win");
    }
    }else {//還沒有選定要移動的區域
    if (this.MyMap.Grid[this.loc[1]][this.loc[0]] ==
    Images.BLANK) {//要移到的位置是一個空白
    }else {//要移到的位置不是空白
    this.selected = true;//設置已選定要移動區域的標志
    }
    repaint();//重新繪圖
    }
    break;
    }
    }

    private boolean win(){
    <A href="file://判">file://判</A>斷是否已經救出了曹操
    if ( this.MyMap.Grid[Images.HEIGHT - 2 ][Images.WIDTH - 3 ] == Images.CAOCAO )
    return true;
    else
    return false;
    }

    private void PrintGrid(String a) {
    <A href="file://打">file://打</A>印當前地圖的內容,用于調試
    System.out.println(a);
    for (int i = 0; i < Images.HEIGHT; i++) {
    for (int j = 0; j < Images.WIDTH; j++) {
    System.out.print( (char)this.MyMap.Grid[i][j]);
    }
    System.out.println("");
    }
    }

    private void Move() {
    <A href="file://將">file://將</A>要移動的區域移動到剛選中的區域
    if (this.MoveArea[0] == -1 || this.MoveArea[1] == -1 ||
    this.SelectArea[0] == -1 || this.SelectArea[1] == -1) {//沒有選中區域
    }else {//已經選中了要移動的區域和要移動到的區域
    byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];
    <A href="file://復">file://復</A>制要移動的區域,因為這塊區域可能會被覆蓋掉
    for (int i = 0; i < this.SelectArea[2]; i++) {
    for (int j = 0; j < this.SelectArea[3]; j++) {
    temp[j][i] =
    this.MyMap.Grid[this.SelectArea[1] +j]
    [this.SelectArea[0] + i];
    }
    }
    <A href="file://PrintGrid">file://PrintGrid</A>("1"); // 調試信息
    <A href="file://將">file://將</A>要移動的區域移動到剛選中的區域(即要移動到的區域)
    for (int i = 0; i < this.SelectArea[2]; i++) {
    for (int j = 0; j < this.SelectArea[3]; j++) {
    this.MyMap.Grid[this.MoveArea[1] + j]
    [this.MoveArea[0] + i] = temp[j][i];
    }
    }
    <A href="file://PrintGrid">file://PrintGrid</A>("2");// 調試信息
    <A href="file://將">file://將</A>要移動的區域中無用內容置成空白
    for (int i = 0; i < this.SelectArea[3]; i++) {
    for (int j = 0; j < this.SelectArea[2]; j++) {
    if (!isInRange2(this.SelectArea[0] + j,
    this.SelectArea[1] + i)) {//該點是不在要移動到
    <A href="file://的">file://的</A>區域之內,需置空
    this.MyMap.Grid[this.SelectArea[1] + i]
    [this.SelectArea[0] + j] = Images.BLANK;
    }else {
    }
    }
    }
    <A href="file://PrintGrid">file://PrintGrid</A>("3");// 調試信息
    this.SelectArea[0] = this.MoveArea[0];//重置選中位置的水平坐標
    this.SelectArea[1] = this.MoveArea[1];//重置選中位置的豎直坐標
    this.MoveArea[0] = -1;//清空要移動到的位置
    this.MoveArea[1] = -1;//清空要移動到的位置
    this.MoveArea[2] = 0;//清空要移動到的位置
    this.MoveArea[3] = 0;//清空要移動到的位置
    }
    }
    }

      代碼的相關分析,在詳細設計階段已經講過,代碼中有比較相近的注釋,請讀者自行研讀分析.將全部的代碼寫好,用wtk2.0自帶的Ktoolbar工具建立一個工程,接下來把去不源文件放到正確位置下,然后點擊build,再點run,就完成了程序的編寫.當然如果有錯誤還要修改和調試.

      七、測試

      作為一個真正的產品要經過單體測試、結合測試和系統測試。由于項目本身簡單,而且大部分代碼已經是相對成熟的,我們跳過單體測試;又由于筆者的實際環境所限,無法搞到Java手機,無法架設OTA服務器,因此我們也只能放棄系統測試。那么就讓我們開始結合測試吧。測試之前要先出一個測試式樣書,也就是測試的計劃。我們將它簡化一下,只測試如下幾種情況:第一、對各種形狀的區域的選擇和移動;第二、臨近邊界區域的選擇和移動;第三、同一區域的反復選擇和反復移動;第四、非法選擇和非法移動。有了測試的目標,接下來的工作就是用wtk2.0自帶的Run MIDP Application工具進行測試。打開這個工具,加載huarongRoad的jad文件,程序就會自動運行,選擇launch上MIDlet1這個程序,華容道游戲就會躍然屏幕之上,接下來的工作就是左三點.右三點,拇指扭扭,來做測試。測試過程中發現任何的問題,立刻發一個bug票給自己,然后就又是痛苦的調試和修正bug,如此如此。

      八.發布

      談到發布,其實是個關鍵,再好的產品不能很好的發布出去也只是個產品而已,變不成商品也就得不到回報.由于筆者的條件所限,這里只能是紙上談兵,不過還是希望能夠使讀者對這一過程有所了解(網上的資料也很多)。

      J2ME的程序發布一般都是通過OTA(Over The Air),你只需要一臺有公網IP的主機和一個普通的web Server就可以了(盡管要求很低,但筆者還是沒有),這里我們以apache為例介紹一下OTA服務的配置,首先是安裝好了apache服務器,然后在conf目錄下找到mime.types文件,在該文件中加入如下兩行

    application/java-archive jar
    text/vnd.sun.j2me.app-descriptor jad

      然后重起apache服務器就可以了。接下來的工作就是修改jad文件中MIDlet-Jar-URL:后面的參數,將它改為URL的絕對路徑,即<A href="http://***/">http://***/</A>huarongroad.jar(其中***是你的域名或IP地址)。在下面就是用java手機下載jad文件,它會自動部署相應的jar文件并加載它。剩下的工作就和在模擬器上操作是一樣的了。

      九、項目總結

      至此,我們已經完成了一個J2ME游戲的全部開發過程,程序中涉及到了調研、分析、設計、編碼、測試和發布等方面的問題,其實在實際的工作中還有很多更為具體的問題,畢竟技術只在軟件開發過程中占據很有限的一部分,這里限于篇幅的限制無法一一具體展開。今后,筆者計劃再寫一篇使用J2ME開發手機屏保的文章,借此機會向讀者展示J2ME動畫技術;然后再寫一篇J2ME網絡應用的文章,做一個類似開心辭典那樣的知識問答游戲,以便向讀者展示J2ME的網絡技術;待這兩方面的技術交待清楚之后,我將引領讀者制作一個稍大一些的游戲。

    posted @ 2005-02-17 01:02 藍色雪焰 閱讀(452) | 評論 (0)編輯 收藏
     

    韓國系MMORPG和EverQuest在怪物智能上的簡單對比

    現在,讓我們假設諸位正在設計一款MMO ARPG中的怪物行動模式。

    在主流的韓國系MMO ARPG中,這些問題一般被如下處理:
    1. 敵人是否會主動攻擊玩家人物?如何判斷?
    分為主動攻擊和被動攻擊兩種類型。前者當玩家人物進入自身周圍一定范圍之后激發攻擊行為。后者當自身被攻擊后激發攻擊行為。

    2. 當怪物面對多個玩家的時候,如何選擇自己的攻擊對象?
    主流的處理方式是攻擊第一個攻擊自己的玩家。

    3. 戰斗過程中,怪物能夠改變自己的攻擊目標嗎?如何判斷?
    基本上不改變。

    4. 當處于不利的情況下,怪物會作出什么行動?
    死戰到底,有些游戲中有逃跑的設定。

    5. 怪物之間能否協作?
    一擁而上。

    在歐美經典MMORPG EverQuest中,是這樣處理的:
    1. 敵人是否會主動攻擊玩家人物?如何判斷?
    通過一個關系表來決定,如果玩家和自身陣營關系惡劣,那么主動攻擊。否則是被動攻擊。(這個關系表是動態的,會根據玩家的行為而改變)

    2. 當怪物面對多個玩家的時候,如何選擇自己的攻擊對象?
    攻擊首先進入自己視野或者攻擊自己的玩家。

    3. 戰斗過程中,怪物能夠改變自己的攻擊目標嗎?如何判斷?
    戰斗中,怪物有一個隱藏的仇恨列表。怪物會攻擊處于自己仇恨列表頂端的玩家,而玩家會增加仇恨的動作包括攻擊、挑釁、醫療同伴、施放魔法……等等。換言之,如果一個法師瘋狂的施放攻擊法術,那么一定會被怪物首先料理。同樣的,如果牧師頻繁的醫療同伴,也會被怪物特殊照顧。(正確的戰斗方式是讓肉盾類角色保證自己永遠處于仇恨列表最頂端,當然這很困難,但是卻很有必要,也很有趣)

    4. 當處于不利的情況下,怪物會作出什么行動?
    逃跑,而且速度相當快。鑒于怪物之間能夠呼喚,所以讓怪物成功跑掉往往比較可怕。有一些法師系的敵人會用施放傳送門的方式逃跑。

    5. 怪物之間能否協作?
    怪物之間的仇恨可以傳遞。如果兩個怪物距離很近,那么很可能一個發現到了你而兩個都沖過來。法師系的怪物也會給同伴加血和一些防御性法術。


    簡單的說,EQ在“人工智能”的范疇上并沒有走多遠,但是在游戲上卻做的很出色。特別是“仇恨列表”,并不復雜的規則衍生出了很多的變化,堪稱天才的設計。當你體會到因為這個限制而衍生出的精彩刺激的游戲過程,便能理解到玩家想要的其實并不僅僅是經驗值而已。

    試試看將“仇恨列表”導入你的游戲,你覺得會發生什么?

    posted @ 2005-02-16 00:00 藍色雪焰 閱讀(380) | 評論 (0)編輯 收藏
    僅列出標題
    共13頁: First 上一頁 5 6 7 8 9 10 11 12 13 下一頁 
     
    主站蜘蛛池模板: 亚洲精品尤物yw在线影院 | 亚洲国产综合AV在线观看| xxxxwww免费| 国产亚洲福利一区二区免费看| 久久国产精品国产自线拍免费| 色播在线永久免费视频| 好猛好深好爽好硬免费视频| 亚洲精品中文字幕乱码| 免费国产精品视频| 日本一卡精品视频免费| 亚洲av永久中文无码精品| 亚洲AV无码国产在丝袜线观看| 久久久久国色AV免费观看| 亚洲欧洲综合在线| AV在线播放日韩亚洲欧| 亚洲精品免费网站| 国产一级a毛一级a看免费人娇| 亚洲午夜久久久久久噜噜噜| 在线观看的免费网站| 中文字幕无码毛片免费看| 亚洲人成网站看在线播放| 亚洲熟妇av一区二区三区| 好男人视频在线观看免费看片| 亚洲精品国产摄像头| 亚洲av不卡一区二区三区| 深夜国产福利99亚洲视频| 四虎最新永久免费视频| 一级毛片a免费播放王色电影| 亚洲毛片av日韩av无码| 美女视频黄免费亚洲| 免费看少妇高潮成人片| 日韩精品无码永久免费网站| 久久亚洲国产成人影院| 亚洲福利视频一区二区三区| 亚洲欧洲日产国码av系列天堂 | 无码AV片在线观看免费| 黄色网址在线免费观看| 亚洲国产美女精品久久| 亚洲av无码乱码国产精品| 在线观看亚洲精品福利片| 免费a级毛片18以上观看精品|