HTML 5街頭霸王游戲(開放源碼)
試玩地址: http://alloyteam.github.com/StreetFighter/
下載地址: https://github.com/AlloyTeam/StreetFighter
主機(jī)控制鍵:
移動(dòng): W: 上, D: 前, A: 后, S:下
攻擊: J: 輕拳, K: 重拳, U: 輕腿, I: 重腿
特殊技能: 下→前→拳: 波動(dòng)拳, 下→后→腿:旋風(fēng)腿, 前→下→前→拳:升龍拳
副機(jī)(小鍵盤):
移動(dòng): ↑: 上, ←: 前, →: 后, ↓:下
攻擊: 1: 輕拳, 2: 重拳, 4: 輕腿, 5: 重腿
特殊技能: 下→前→拳: 波動(dòng)拳, 下→后→腿:旋風(fēng)腿, 前→下→前→拳:升龍拳
其他:
按F2暫停游戲, 1鍵大戰(zhàn)電腦ai, 2鍵雙人對(duì)打.
如果控制不了, 注意切換下輸入法哈.
圖片素材來自互聯(lián)網(wǎng), 原作者Random. 游戲版權(quán)歸CAPCOM公司所有.歐洲杯之前加上websocket和3D音效.
開發(fā)過程介紹
大概是1個(gè)月前開始學(xué)習(xí)HTML5, 就寫了這樣一個(gè)東東練手. 不過說來慚愧, 至今也只學(xué)會(huì)了canvas的drawImage. 每天的業(yè)余時(shí)間不太固定, 有時(shí)候一天能寫三小時(shí), 有時(shí)候一天能寫三分鐘. 代碼也寫的相當(dāng)潦草. 總的來說有點(diǎn)虎頭蛇尾. 本來準(zhǔn)備找個(gè)時(shí)間再重構(gòu)一下, 突然發(fā)現(xiàn)失去了興致, 歐洲杯又馬上開始了. 對(duì)我來說, 已經(jīng)差不多達(dá)到練手的目的, 所以還是罷了.
非常簡(jiǎn)單的記錄一些實(shí)現(xiàn)思路. 暫且不討論api. 一是因?yàn)閍pi到處可以查閱, 二是因?yàn)槲掖_實(shí)只認(rèn)識(shí)drawImag. 本人技術(shù)也十分有限, 請(qǐng)輕砸.
代碼里只有幾個(gè)js文件, 每個(gè)文件的功能如下:
Class.js 創(chuàng)建類和對(duì)象.
Game.js 游戲入口文件.
Map.js 繪制地圖.
Config.js 各種游戲人物動(dòng)作打架挨打等等配置.
Interface.js 各種接口
Main.js 負(fù)責(zé)游戲邏輯
Ai.js Ai
Timer.js 全局定時(shí)器
Class.js.
為什么要搞這樣一個(gè)東西呢. 保護(hù)原型, 繼承的時(shí)候修正constructor什么的, 反正現(xiàn)在不搞個(gè)Class.create都有點(diǎn)不太好意思.
這里也借鑒了prototype框架里的一些思路. 相對(duì)于prototype里的換湯不換藥. 這兒的class.create選擇返回一個(gè)普通的object對(duì)象, 有點(diǎn)像jquery里$的搞法.
這樣可以自由的擴(kuò)展Class的各種方法,而不用再搭理Function的原型. 舉個(gè)例子, Class.empty()可以秒殺這個(gè)類生成的所有對(duì)象. 比如現(xiàn)在正在設(shè)計(jì)一個(gè)飛機(jī)游戲. 有個(gè)大招可以清除屏幕上的所有子彈和敵機(jī). 那么, 哼哼..
因?yàn)镃lass.create返回的是一個(gè)普通的object. 所以不能用new Function的方式生成對(duì)象. 具體使用方法如下例.
#p#
- var Hero = Class.create( function( name ){
- this.name = name;
- } , {
- addSkill: function( skill ){
- .......
- }
- })
- var hero1 = Hero.getInstance( "半人馬酋長(zhǎng)" );
- hero1.addSkill( "六級(jí)跳大" );
- var hero2 = Hero.getInstance( "山嶺巨人" )
- hero2.addSkill( "六級(jí)跳大" )
- var hero3 = Hero.getInstance( "黑暗游俠" );
- hero3.addSkill( "六級(jí)跳大">" );
Timer.js
用js做動(dòng)畫, 無非就是用setInterval或者setTimeout讓圖片的top和left, 或者圖片本身的src在很短的時(shí)間內(nèi)間隔變換. 達(dá)到視覺的動(dòng)畫效果. 跟動(dòng)畫片一樣.
雖然屏幕上有很多精靈在同時(shí)運(yùn)動(dòng). 但在一個(gè)游戲中, 只有也應(yīng)該只有一個(gè)全局的定時(shí)器.
一是從性能出發(fā). setInterval的開銷相當(dāng)不小.
二是為了統(tǒng)一管理, 比如方便的實(shí)現(xiàn)暫停功能.
北京時(shí)間X點(diǎn)X分X秒X毫秒, 東經(jīng)X度北緯X度在發(fā)生什么. 整個(gè)世界就是這樣組成.
定時(shí)器從游戲開始一直在不停執(zhí)行, 像地鐵環(huán)線. 每隔1小時(shí)回到起點(diǎn). 也像我們自己, 每隔24小時(shí)回到原點(diǎn), 周而復(fù)始.
- var timer = Timer.add( function(){
- alert (1)
- })
- timer.start(); //上車
- timer.stop(); //下車
- timer.slow( 1000 ) 地鐵減速
Interface.js
我之前寫過幾個(gè)小游戲, 每個(gè)都搞了很多類在里面. 比如坦克大戰(zhàn), 子彈是一個(gè)類, 移動(dòng)是一個(gè)類. 碰撞是一個(gè)類. 坦克先繼承碰撞類, 再繼承移動(dòng)類. 這樣一來坦克既能碰撞也能移動(dòng). 非常酷.
可是真的需要那么多類么. 有種方式或許更輕巧敏捷, 那就是接口.
讓精靈可以移動(dòng)只需要Spirit.interface( ’Animate’, Interfaces.Animate );
上帝創(chuàng)造生命的時(shí)候, 沒有讓腔腸動(dòng)物和環(huán)節(jié)動(dòng)物實(shí)現(xiàn)眼睛的接口.
人類當(dāng)然實(shí)現(xiàn)了眼睛的接口, 但人類不是從眼睛繼承來的.
接口在未被聲明之前, 只是一個(gè)普通的函數(shù), 沒有構(gòu)造器, 沒有prototype. 基本不占內(nèi)存開銷.
對(duì)于每個(gè)宿主( 實(shí)現(xiàn)者 )來說, 比如精靈1的animate和精靈2的animate. 它們像兩個(gè)平行的宇宙. 每個(gè)都有各自的scope. 局部變量.
接口之間不贊成互相通信. 但可以通過宿主來通信. 就像人類實(shí)現(xiàn)了耳朵接口和嘴巴接口.
耳朵聽到聲音先把信息報(bào)告給大腦. 大腦再控制嘴巴說話. 但耳朵和嘴巴是不應(yīng)該長(zhǎng)到一起的. 這樣不至于耳朵壞了的時(shí)候要修理嘴巴. 其實(shí)就是三個(gè)字, 要解耦!
Intanfances里面的主要接口有這些:
Event: 一個(gè)簡(jiǎn)單的自定義事件機(jī)制, 以便在Animate,Frame和碰撞檢測(cè)的時(shí)候?qū)崿F(xiàn)有限狀態(tài)機(jī).
Lock: 動(dòng)作鎖.
Queue: 一個(gè)簡(jiǎn)單隊(duì)列機(jī)制.
StatusManage: 管理精靈的各種狀態(tài).
Shadow: 精靈的陰影.
Animate: 移動(dòng).
SpiritFrames: 精靈的動(dòng)畫幀.
KeyManage: 鍵盤管理器, 收集玩家輸入.
Collision: 檢測(cè)碰撞.
AttackEffect: 攻擊效果.
Audio: 音效.
Main.js
游戲的具體邏輯都在里面, 這個(gè)模塊里一共實(shí)現(xiàn)了三個(gè)類. 精靈類, 戰(zhàn)斗類, 還有一個(gè)類有點(diǎn)別扭, 它是波動(dòng)拳類. - -!.
整個(gè)游戲里也只有這3個(gè)類. 不過因?yàn)檫壿嬢^多. 時(shí)間消耗基本都花在了這里. 畢竟它不像貪食蛇, 只要判斷食物, 墻壁和尾巴.
Ai.js
這個(gè)模塊負(fù)責(zé)Ai所有邏輯, 也是寫的最輕松的一個(gè)模塊. 寫好之后基本沒改過.
對(duì)于電腦ai來講, 它明白對(duì)方的每一個(gè)動(dòng)作. 所以對(duì)每一組動(dòng)作, 都給ai設(shè)計(jì)了一組反應(yīng)動(dòng)作. 比如你出旋風(fēng)腿, 電腦就出升龍拳.
但這樣的話就沒人打的過電腦了, 所以電腦的每次反應(yīng)都有一組對(duì)的和一組錯(cuò)的, 可以調(diào)節(jié)ai的難度, 當(dāng)ai越難的時(shí)候, 隨到正確那組動(dòng)作的可能性越大.
寫游戲和寫普通的應(yīng)用有點(diǎn)不一樣的地方是, 游戲需要更好的抽象出每個(gè)場(chǎng)景之間的共同點(diǎn), 或者找出他們的不同點(diǎn). 要盡量盡量少寫if else. 除非是逼不得已. 當(dāng)你寫了一個(gè)if, 就意味著可能要寫N個(gè)else if. 當(dāng)邏輯越來越多的時(shí)候, 維護(hù)這些if會(huì)異常痛苦.
街頭霸王里面的動(dòng)作有很多種, 比如跳躍的時(shí)候不能移動(dòng), 攻擊的時(shí)候既不能跳躍也不能移動(dòng), 跌倒的時(shí)候既不能跳躍也不能攻擊也不能移動(dòng). 死亡之后啥也干不了. 那么怎么處理這些邏輯呢. 想想如果是寫這樣的代碼.
- If ( isJump ){
- If ( move ) return false;
- }else if( isAttack ){
- If ( move || jump ) return false;
- }else if ( fall_down ){
- If ( move || jump || attack ) return false;
- }...
游戲里的具體邏輯比這復(fù)雜的多, 我也想不到得寫多少個(gè)if else, if else. 閉上眼睛就是if else.
現(xiàn)在我是這樣實(shí)現(xiàn)的. 給每種動(dòng)作在配置文件里加一個(gè)鎖. 精靈在動(dòng)的時(shí)候, 總是被它鎖住的. 移動(dòng)的鎖是0級(jí), 跳躍是1級(jí), 攻擊是2級(jí). 摔倒是3級(jí).
當(dāng)要執(zhí)行一個(gè)新的動(dòng)作的時(shí)候, 比如攻擊的時(shí)候突然被踢倒. 會(huì)先比較2個(gè)動(dòng)作的鎖的級(jí)別. 如果后面動(dòng)作的級(jí)別大于之前動(dòng)作的級(jí)別. 就會(huì)打破之前的鎖, 執(zhí)行新的動(dòng)作. 反之會(huì)無視新的動(dòng)作. 比如攻擊的時(shí)候移動(dòng)和跳躍都是沒用的.
原文鏈接:http://www.alloyteam.com/2012/05/html5-streetfighter-demo/