HTML5 2D平台遊戲開發——地圖繪製篇

來源:互聯網
上載者:User

標籤:prot   sheet   pos   架構   結束   post   分區   x64   範圍   

  此前已經完成了一部分角色的動作,現在還缺少可以互動的地圖讓遊戲看起來能玩。不過在開始之前應當考慮清楚使用什麼類型的地圖,就2D平台遊戲來說,一般有兩種類型的地圖,Tile-based和Art-based,即基於瓦片風格和美術風格兩種。Tile-based的典型代表是《Super Mario》(超級馬里奧),Art-based記不太清楚了,能夠回想起來的是去年出的一款叫做《Owlboy》(貓頭鷹男孩)的遊戲。

   
 Super Mario  Owlboy

由於Art-based的實現需要一款較為專業的可視化遊戲開發工具,因此我選擇較為傳統的Tile-based。

 

  • 瓦片尺寸的選擇

   通常可選的瓦片尺寸有16X16、32X32、64X64、128X128等,都是2的N次方。原因之一當然是比較好計算,另外一個原因是電腦對2的倍數計算速度有比較明顯的最佳化。這裡採用32X32的尺寸。

遊戲中暫時用到的瓦片集合

 

  • 將瓦片集合製作成地圖

   Tiled Map Editor是一款非常強大的地圖編輯軟體,體積不大而且還是開源的,是地圖編輯的首選。

 

編輯中的地圖

可以添加多個圖層來表現遊戲地圖的層次,這裡使用兩個圖層,背景層和地面層。經過考慮,我暫時不打算使用對象層,故而上面的勾去掉了。雖然添加這個層可以很方便地得到碰撞資料,但需要對每個碰撞的部分建立包圍盒,當地圖很大並且很多的時候就十分耗費時間去添加和維護,後期會在添加角色出生點或者尋路時才使用它。

 

  • 匯出地圖資料

  將地圖資料匯出為JSON格式,得到類似下面的資料:

layers:[    [3, 3, 3, 8, 9, 3, 3, 3, 3, 3, 3, 3...],//背景層    [-1, -1, -1, -1, -1, -1,-1, -1, 10...]//地面層]

其中的數字代表瓦片在瓦片集合中的索引,不過需要注意索引是從1開始的,而一般我們計算時都喜歡從0開始,可以在代碼中將其減去1或者用數組的map方法處理一下並覆蓋未經處理資料。同時建立一個列表來標註瓦片的類型:

MAPCONFIG.FIELDTYPE = {    ‘solid‘: [0, 1, 2, 13, 14, 15, 26, 27, 28, 30, 31, 32, 33, 34, 35],    ‘null‘: [3, 5, 6, 7, 8, 9, 11, 12, 16, 17, 18, 19, 22, 23]};

根據瓦片的數字來判斷它是否需要建立碰撞包圍盒,這樣就省去了上述建立對象層的步驟。

 

  • 在畫布上繪製地圖

  上面已經得到了地圖資料,可以開始進行地圖的繪製了。建立一個地圖管理函數MapManager()

class MapManager {    constructor(level, ctx, assets) {        this.spriteSheet = assets.image;        //416,96 所採用圖片資源的寬高        this.dimensions = {w: assets.w, h: assets.h};        this.level = level;        this.layerLength = this.level.layers.length;        this.ctx = ctx;    }    //prototype ...}

接下來需要一個擷取瓦片代表數位方法getTile(),這個方法返回像3,5,-1這樣的數字,即瓦片在圖片上的索引。

//layerIndex 圖層索引//col 瓦片所在列的編號//row 瓦片所在行的編號getTile(layerIndex, col, row) {    //this.level.cols為整個地圖的列數    return this.level.layers[layerIndex][row * this.level.cols + col];}

_drawLayer()方法繪製一個層:

let startCol = 0,//起始列endCol = 40,//結束列startRow = 0,//開始行endRow = 20,//結束行tileSize = MAPCONFIG.TILESIZE;//32for (let r = startRow; r < endRow; r++) {    for (let c = startCol; c < endCol; c++) {        let tile = this.getTile(layerIndex, c, r),        x = (c - startCol) * tileSize, //瓦片的x座標        y = (r - startRow) * tileSize; //瓦片的y座標        if (tile !== -1) {//-1代表空瓦片不繪製            this.ctx.drawImage(
        this.spriteSheet,
        tile * tileSize % this.dimensions.w, //瓦片精靈圖上的x座標  Math.floor(tile * tileSize / this.dimensions.w) * tileSize, //瓦片精靈圖上的y座標        tileSize, tileSize,
        Math.round(x), Math.round(y),
        tileSize, tileSize
       ); } }}

_drawMap()方法繪製多個層:

_drawMap() {  this.level.layers.forEach((layer, index) => this._drawLayer(index));}

最後使用render()方法進行繪製:

render() {    this._drawMap();}

最終得到下面的地圖:

這類繪製地圖的方法在很多遊戲中也通用,我曾經在《使用HTML5製作簡單的RPG遊戲》裡也使用了同樣的地圖資料格式,不過那時是依靠架構完成的,裡面具體發生了什麼自己並不清楚,現在正好回顧和總結一下。

 

  • 處理地圖與角色的碰撞

  現在地圖還只是單純的視角效果,並不能與角色產生互動,需要將各個瓦片進行標註,以表明它到底是不能通過的固體還是可以通過的空瓦片。在此之前,需要先瞭解一個概念,即axis-aligned bounding box,翻譯過來就是軸對齊邊界盒子,簡稱AABB。通過為瓦片與角色建立AABB,可以很方便地檢測它們之間是否碰撞,這一概念在之前寫過的一篇文章《Chrome內建恐龍小遊戲的源碼研究(七)》中也有涉及到。至於更為複雜的碰撞檢測,比如SAT,現在還沒有深入研究,後續會逐步加入遊戲中。

AABB的實現代碼:

class AABB {    /**     * 碰撞盒子     * @param x    {number} 盒子x座標     * @param y    {number} 盒子y座標     * @param w    {number} 盒子寬度     * @param h    {number} 盒子高度     */    constructor(x,y,w,h) {        this.pos = new Vector(x,y);        this.size = new Vector(w,h);        this.center = new Vector(this.pos.x + w / 2,this.pos.y + h / 2);        this.halfSize = new Vector(this.size.x / 2,this.size.y / 2);    }}

其中又涉及到向量的概念(累??),向量在2D和3D遊戲中都有著大量的應用,屬於硬知識。下面為瓦片添加AABB,回顧MapManager方法,為其添加一個儲存AABB的數組:

constructor(level, ctx, assets) {    this.gridForAABB = [];    //......}_drawLayer(layerIndex) {    let startCol = 0,   //起始列        endCol = 40,    //結束列        startRow = 0,   //開始行        endRow = 20,    //結束行        tileSize = MAPCONFIG.TILESIZE,        isSolidLayer = layerIndex === (this.layerLength - 1),        grid = [];    for(let r = startRow; r < endRow; r++) {        for (let c = startCol; c < endCol; c++) {            let tile = this.getTile(layerIndex, c, r),                x = (c - startCol) * tileSize,  //瓦片的x座標                y = (r - startRow) * tileSize;  //瓦片的y座標            //......            if (isSolidLayer) {                //如果遇到的瓦片是固體                if (MAPCONFIG.FIELDTYPE[‘solid‘].includes(tile)) {                    //為其建立AABB                    grid.push(new AABB(c, r, 1, 1));                } else grid.push(tile);  //存入普通瓦片                    }        }    }    //將所有瓦片的AABB資料儲存起來    if (isSolidLayer) this.gridForAABB = grid;}    

如同上面的getTile()方法一樣,得到一個瓦片的AABB可以用如下代碼:

gridForAABB[row * this.level.cols + col]

最後,需要將其與角色的位置聯絡起來。

 

  • 擷取影響角色位置的瓦片

  首先觀察一張圖

假設玩家在地圖裡向右移動,此時角色佔據的空間為藍色部分地區,因此需要在這個範圍內尋找有沒有障礙物阻止角色移動,在MapManager()函數中新增一個方法:

    obstacleAt(layerIndex, pos, size) {        let startCol = Math.floor(pos.x), //player‘s x position             endCol = Math.ceil(pos.x + size.x), //pos.x plus player‘s width             startRow = Math.floor(pos.y),             endRow = Math.ceil(pos.y + size.y);        for (let c = startCol; c < endCol; c++) {            for (let r = startRow; r < endRow; r++) {                let tile = this.getTile(layerIndex, c, r),                    fieldType = null;                if (MAPCONFIG.FIELDTYPE[‘solid‘].includes(tile)) {                    fieldType = ‘solid‘;                }                if (fieldType) return {col: c, row: r, fieldType: fieldType};            }        }        return null;    }

一旦在這個地區找到障礙物,立即返回它。

接下來需要處理角色與障礙物的碰撞:

假設角色在移動中碰到了右邊的障礙物(橘色部分),此時應當修正角色的位置避免繼續碰撞:

以下是測試中的效果:

不過一些細節還需要調整,有時間的話會繼續更新。

 

更新日誌

  2017/04/09  更新角色跳躍

  2017/04/21  更新角色衝刺

  2017/05/01  更新角色狀態機器

  2017/05/16  更新角色攻擊動畫

  2017/05/22  更新角色移動攻擊動畫

  2017/05/24  更新角色跳躍攻擊動畫

  2017/06/04  更新地圖繪製

HTML5 2D平台遊戲開發——地圖繪製篇

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.