我用 JS 開發一款超級瑪麗遊戲

作者:@hhzzcc

https://juejin.cn/post/739211607567482882

前言

從剛接觸編程起,作者就一直有一個做遊戲的夢,奈何水平比較差,做不出啥太像樣的遊戲,最近趁着下班和週末時間折騰折騰,懷舊一下童年最愛玩的超級瑪麗。本文將講述如何用js寫一個MVP版本的超級瑪麗遊戲,主要用到框架有leaferjs + vueleaferjs負責做圖形渲染,vue負責界面和地圖編輯,leaferapi用起來還是很舒服的,學習成本低,作者也非常熱情,這裏幫忙推薦一下~

本文會盡可能簡單的少貼代碼,並把核心的流程講清楚,先上一張運行後的效果圖,源碼和體驗地址在最下面

開始我們的遊戲夢

1、創建背景

就是遊戲背後的藍天白雲背景,背景素材如下

在遊戲中,背景會隨人物前進而後退,但是由於素材寬度有限,不可能無限後退,所以做了個邏輯,當背景的x到達邊界的時候,把x設置成0,達到類似無限循環背景的效果

這裏使用leaferGroup + Rect實現,由一個Background類來實現,用leafer實現簡單很多

2、創建場景

場景用來承接和繪製遊戲中的精靈,所以需要有個存放精靈的數組,以及一個run方法來繪製精靈(這裏使用leaferCanvas實現),具體要繪製什麼,解耦給各個精靈內部自由實現

class Scene {
  sprites = [];
  
  // 添加內容
  add(sprite) {
    this.sprites.push(sprite);
  }
  
  run() {
    // 繪製內容,傳入canvas上下文
    this.sprites.forEach((sprite) ={
      sprite.draw(context)
    })
    ...
  }
}

3、創建精靈

精靈是指場景中的元素,比如瑪麗、磚塊、成長蘑菇、敵人蘑菇等,我們可以爲這些精靈創建一個基類Sprite,有xy表示位置,有widthheight表示寬高,vxvy表示水平和垂直方向速度

class Sprite {
  constructor(options) {
    const { x, y, width, height, vx, vy } = options;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.width = width;
    this.height = height;
  }
}

場景中所有精靈都可以繼承於他,同時上面也說到了繪製精靈精靈自身來決定,所以有個draw方法,且由於受到速度的影響,精靈的xy需要做更新

class SpriteMario extends Sprite {
  constructor(options) {
    super(options);
    // ...
  }
  
  draw(context) {
    this.x += this.vx;
    this.y += this.vy;
    context.drawImage(this.resource, this.x, this.y, this.width, this.height)
  }
}

精靈需要根據自身的狀態在當前類內維護自己的動畫幀,比如當瑪麗的vx > 0,要繪製的圖片就是瑪麗向右走的動畫幀,實現後,效果如下(這裏我降低了gif的幀數,實際運行是很流暢的,下面的gif也是)

然後我們依次繪製出其他磚塊、石塊、道具等精靈,這裏先提前說一下,背景的運動、場景的繪製、包括底下的相機跟隨、物理引擎都在每一幀即requestAnimationFrame中執行,代碼如下

run() {
    // 運行物理引擎
    this._physicsEngine.run({
      camera: this.camera,
      scene: this.scene,
    });

    // 運行場景中的精靈
    this.scene.run();

    // 運行背景
    this._background.run();

    // 相機跟隨瑪麗
    this.camera.x =
        this._mario.x < MARIO_VIEW_OFFSET
          ? 0
          : this._mario.x - MARIO_VIEW_OFFSET;

    requestAnimationFrame(this.run.bind(this));
  }

實現後,效果如下

看起來很有感覺有沒有!接下來開始做物理引擎

4、物理引擎 - 重力

最先要實現的當然就是重力拉,運用初中物理知識,使用公式套進去

class PhysicsEngine {
  run (options) {
    // ...
    
    // 爲了防止過快給了個10的速度閾值
    sprite.vy = Math.min(10, sprite.vy + G);
  }
}

效果如下

5、物理引擎 - 碰撞檢測

加上重力後,瑪麗會往下掉,然後來實現碰撞檢測,這裏的思路比較簡單,用到的是矩形之間的碰撞檢測,先校驗兩個矩形是否發生碰撞,如果發生碰撞在校驗碰撞來源的方向,並根據方向做位置修補,防止碰撞後嵌入,感興趣可以看看源碼,加上之後的效果如下

6、物理引擎 - 跳躍

跳躍套用上拋運動公式實現,給他一個默認的初速度v0

  sprite.v0 -= G;
  sprite.vy = -sprite.v0;

然後當長按 "上鍵" 的時候,增大v0,達到按得越久跳的越高的效果,效果如下

7、物理引擎 - 其他精靈間的碰撞

這裏就挑幾個主要的來講,其他實現都大同小異

7.1、人物頂到問號

問號中的道具會緩緩升起,前者的實現邏輯是給個參數active來指定當前道具精靈狀態,當activefalse不會受到物理引擎影響,所以我們可以在問號被碰撞後,讓道具y值不斷變小,當完全露出時設置其activetrue,並給道具一個vx,效果如下

順便說一下,當瑪麗的頭碰撞到建築的底部時,會取消其上拋運動狀態,使其只受重力影響

7.2、人物喫成長蘑菇

這個就比較簡單了,當人物和蘑菇發生碰撞,將蘑菇銷燬,人物的height變高,然後更新人物動畫幀,效果如下

7.3、人物頂碎磚塊

當人物的頂部碰撞到磚塊的底部時,磚塊銷燬,然後將磚塊拆成4個,向四周做拋物線運動,拋物線軌跡實現主要用一元二次方程實現,這裏貼下核心代碼

  this.animatedX += 2;
  this.animatedY = 0.1 * this.animatedX * this.animatedX - b * this.animatedX;

運行後,效果如下

8、引入相機

這裏的相機不是 webgl 的相機,可以理解成視圖窗口,引入這個概念的目的是爲了讓繪製和物理引擎的執行只有在視口內纔會執行,即只會繪製相機視口的內容,然後我們讓相機的 x 跟隨瑪麗,這樣就能達到跟隨人物前進的效果

  camera.x = mario.x

效果如下

9、 引入分數系統

擊殺怪物、喫道具、頂碎石磚,會出現數字,數字會跟隨對應精靈,這裏用的是leaferText很輕鬆就能實現,效果如下

10、增加勝利機制

創建旗幟,當人物和旗幟發生碰撞後,判定勝利,效果如下

11、編輯地圖

遊戲做完後,要開始做地圖了,由於地圖要一個個手動去敲xy很麻煩,就用vue做了個地圖編輯器,可以通過鼠標來創建精靈自定義關卡,效果如下

結語

上面只是一個超級瑪麗的MVP版本,還有挺多沒實現的,比如音樂、烏龜等等,勝利的動畫也比較簡陋,後續如果有空再慢慢完善他,源碼已經發布github,這裏是源碼地址 1 和體驗地址 2,喜歡的話求個star~

參考資料

[1]

源碼地址:https://github.com/hhzzcc/super-mario

[2]

體驗地址:https://hhzzcc.github.io/super-mario/dist/index.html#/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2o1bNadKVEUNlN6Bv9iC7g