走進互動營銷三:手把手教王夢佳擼個簡單的切水果遊戲

「本文已參與好文召集令活動,點擊查看: 後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」 技術棧:canvasmatter-jsTS
前端

本篇文章原本是在內部分享的,人名都是同事名字。git


王夢佳:「熊東起GG,我想吃水果~」
熊東起:「這哪受得了,給你切」github

先看看效果web

切西瓜gif.gif

廢話話很少說,直接開整!typescript

文章索引

完整代碼:github.com/superBlithe…canvas

方案後端

  • step1: 初始化&簡單佈局
  • step2: 刀光實現
  • step3: 刀碰撞水果,matterjs登場
    • matterjs結合FYGE
    • 重力
    • 碰撞
  • step4: 物理引擎&碰撞檢測
  • step5: 遊戲頁

方案

換個思路,咱們先來拆解,而後說實現。就像拼積木同樣,逐步句完成。緩存

  • 個場景:開始頁遊戲頁
  • 個關鍵的類:水果刀光
  • 物理引擎:重力、碰撞檢測

可跳過step1

step1: 初始化&簡單佈局

王夢佳說:「我啥也不會,咋辦呢~」
熊東起:那咱們就從0開始吧~markdown

已經提早給你們準備好了初始項目

你們能夠克隆基礎項目:github.com/superBlithe…dom

基礎項目都幹了啥呢?

  • 各種圖片
  • 建立目錄
  • 入口文件main.ts加載這些圖片,後面只須要從緩存取就行了
  • 封裝一些方法

src項目目錄:

  • components:存放各種組件,好比背景、刀光、水果
    • GameBg.ts
  • config
    • GameCfg.ts 遊戲配置
    • GUtils.ts 工具方法
  • GameScene
    • GameScene.ts 遊戲頁
    • StartScene.ts 開始頁
  • main.ts 入口,文件

開始遊戲按鈕

開始按鈕實際上是分爲圓環和西瓜🍉

聲明類型

export class StartScene extends FYGE.Container {
  /** 開始按鈕 */
  private startBtnGroup: FYGE.Container;
  private btnOut: FYGE.Sprite;
  /** 水果:西瓜》 開始按鈕 */
  private xigua: Fruit;
  /** 西瓜的動畫 */
  private TweenXigu: FYGE.Tween;
複製代碼

真正的按鈕西瓜須要是一個水果

咱們這裏先把圓環及圓環的動畫

讓它逆時針轉起來

private initBtn() {
    this.startBtnGroup = this.addChild(new FYGE.Container());
    this.startBtnGroup.position.x = 200;
    this.startBtnGroup.position.y = 350;
    let btnOut = this.startBtnGroup.addChild(new FYGE.Sprite(getRes(RES_MAP.newGameBtnOut)));
    btnOut.anchorX = btnOut.anchorY = btnOut.width / 2;
    FYGE.Tween.get(btnOut, { loop: true }).to({ rotation: -360 }, 8000);
    this.btnOut = btnOut;
  }
複製代碼

簡單水果類

爲何叫簡單水果類?由於這裏僅僅是個展現的節點 開始頁的西瓜,以及遊戲頁的各種水果。都會從這個類進行擴展。 咱們先建立一個簡單的水果類。 新建components/Fruit.ts

import { FRUIT_NAME, RES_MAP, SS } from "../config/GameCfg";
import { ADD_SPRITE, getRes, GUtils } from "../config/GUtils";

export default class Fruit extends FYGE.Sprite {
  constructor() {
    super();
    /** 防止貼圖禁用後,後面沒辦法複用。 */
    this.texture = getRes(RES_MAP["fruitXigua"]).clone();
    this.anchorX = this.width / 2;
    this.anchorY = this.height / 2; 
  }
}
複製代碼

而後呢,在開始頁引入進來,並進行聲明

/** 水果:西瓜》 開始按鈕 */
  private xigua: Fruit;
複製代碼

而後再initBtn方法中進行初始化,並添加順時針旋轉的動效

let xigua = this.addChild(new Fruit());
  xigua.x = 250;
  xigua.y = 400;
  this.TweenXigu = FYGE.Tween.get(xigua, { loop: true }).to({ rotation: 360 }, 4000);
  this.xigua = xigua;
複製代碼

基礎搭建完事了,咱們下面會進行刀光的繪製

step2: 刀光實現

王夢佳:」好嚇人喔~,還有刀光!「
熊東起:」必須的啊,不光開始頁有,遊戲頁也要有,並且還要參與後面的碰撞檢測呢~「

刀光實際上是畫的多邊形。drawPolygon能夠接受一組座標Points

鼠標移動的時候,進行捕獲座標。 鼠標擡起的時候,清除繪製內容。

那就開始吧~ 新建components/Blade.ts

import { RES_MAP, SS } from "../config/GameCfg";
import Tpoint from "./Tpoint";

//每一個點存活時間
const POINTLIFETIME = 100;
export default class Blade extends FYGE.Graphics {
  private points: Tpoint[] = [];

  public drawBlade(e: FYGE.MouseEvent) {
    this.clear();
    let point = new Tpoint(e.localX, e.localY);
    point.time = new Date().getTime();
    this.points.push(point);
    if (new Date().getTime() - this.points[0].time > POINTLIFETIME) {
      this.points.shift();
    }
    // 點太少,誤觸
    if (this.points.length < 2) return;
    this.beginFill(0xffffff);
    this.drawPolygon(this.points);
    this.endFill();
  }

  public reset() {
    this.points = [];
    this.clear();
  }
}
複製代碼

王夢佳:」Tpoint是個啥啊?「
熊東起:」就是個普通的Point, 擴展一個time字段。你本身新建吧。「
熊東起:」咱們要添加事件咯。「

StartScene.ts

/** 刀光 */
  private blade: Blade;
  // init() 裏添加
  this.blade = this.addChild(new Blade());

/** 監聽事件 */
  private addEvents() {
    this.stage.addEventListener(FYGE.MouseEvent.MOUSE_MOVE, this.onMouseMove, this);
    this.stage.addEventListener(FYGE.MouseEvent.MOUSE_UP, this.onMouseUp, this);
  }

  /** 移除事件 */
  private removeEvents() {
    this.stage.removeEventListener(FYGE.MouseEvent.MOUSE_MOVE, this.onMouseMove, this);
    this.stage.removeEventListener(FYGE.MouseEvent.MOUSE_UP, this.onMouseUp, this);
  }

  private onMouseUp() {
    this.blade.reset();
  }

  /** 鼠標移動 */
  private onMouseMove(e) {
    _throttle(this.blade.drawBlade(e), 50);
    // this.blade.checkCollide(this.xigua, this.doStart.bind(this));
  }
複製代碼

step3: 刀碰撞水果,matterjs登場

若是隻是開始按鈕的西瓜碰撞檢測,咱們能夠直接使用FYGE的點和物體sprite.hitTestPoint的方法。事實上我剛開始也是這麼作的。 可是後面還要模擬重力,模擬物體和物體碰撞。因此就引入了matterjs。 安裝matterjs

yarn add matter-js @types/matter-js -S
複製代碼

怎麼使用呢?

以前打磚塊文章有聊過一些用法。能夠結合參考。咱們這裏稱matterjs建立的爲物理世界,FYGE建立的稱爲視圖世界

  • 首先咱們要建立一個物理世界
  • 而後呢? 在物理世界畫一些剛體,和當前的FYGE裏面的節點綁定起來
  • 監聽物理世界,改變視圖世界

建立物理世界

隱藏背景(方便調試),而後執行建立物理世界方法, 調試的時候把render渲染打開

init()

// this.addChild(new GameBg());
  this.createPhyWorld()
複製代碼

createPhyWorld()

/** 建立物理世界 */
   private createPhyWorld() {
    const { Engine, Render, Runner, Composite, Bodies, World, Composites } = Matter;
    this.engine = Engine.create();
    this.world = this.engine.world;
     this.engine.gravity.y = 0;
     
    /** 真正運行 */
    this.runner = Runner.create();
    Runner.run(this.runner, this.engine);
    // @ts-ignore
    this.composites = Composite;
     
    // 臨時渲染引擎,調試用
    var render = Render.create({
      element: document.body,
      engine: this.engine,
      options: {
        width: 750,
        height: 600,
      },
    });
    Render.run(render);
  }
複製代碼

增長物理世界的刀光

咱們只須要擴展Blade類,而後增長一個屬性PhyBody。 在繪製刀光的時候,同時在物理世界繪製一份。 咱們在每次繪製drawBlade方法以前都要清除以前的繪製。

SSSS = document.body.clientWidth / 750; 爲了同步兩個世界座標的換算比例。

完整的刀光代碼 Blade.ts

import * as Matter from "matter-js";
import { RES_MAP, SS } from "../config/GameCfg";
import Tpoint from "./Tpoint";

//每一個點存活時間
const POINTLIFETIME = 100;
export default class Blade extends FYGE.Graphics {
  private points: Tpoint[] = [];
  private _body: Matter.Body;
  get phyBody(): Matter.Body {
    return this._body;
  }
  set phyBody(v: Matter.Body) {
    this._body = v;
  }

  public drawBlade(e: FYGE.MouseEvent) {
    this.clear();
    let point = new Tpoint(e.localX, e.localY);
    point.time = new Date().getTime();
    this.points.push(point);
    if (new Date().getTime() - this.points[0].time > POINTLIFETIME) {
      this.points.shift();
    }
    // 點太少,誤觸
    if (this.points.length < 2) return;
    this.beginFill(0xffffff);
    this.drawPolygon(this.points);
    this.endFill();
    // @ts-ignore
    this.phyBody && this.parent.composites.remove(this.parent.world, [this.phyBody]);
    // 物理世界也跟着一塊兒畫
    this.phyBody = Matter.Bodies.fromVertices(
      e.localX * SS,
      e.localY * SS,
      [
        this.points.map((p) => {
          let { x, y } = p;
          return { x: x * SS, y: y * SS };
        }),
      ],
      {
        isStatic: true,
      }
    );
    // @ts-ignore
    this.parent.composites.add(this.parent.world, [this.phyBody]);
  }

  public reset() {
    this.points = [];
    this.clear();
    // @ts-ignore
    this.phyBody && this.parent.composites.remove(this.parent.world, [this.phyBody]);
  }
}
複製代碼

刀光就成了~

也給水果-西瓜添加一個物理剛體

方法跟刀光同樣,這裏直接貼代碼了

export default class Fruit extends FYGE.Sprite {
  public phyBody: Matter.Body;
  constructor() {
    super();
    /** 防止貼圖禁用後,後面沒辦法複用。 */
    this.texture = getRes(RES_MAP["fruitXigua"]).clone();
    this.anchorX = this.width / 2;
    this.anchorY = this.height / 2; 
    this.phyBody = Matter.Bodies.circle(this.x * SS, this.y * SS, (this.width / 2) * SS, {
      isStatic: true,
      isSensor: true, // 傳感器,能夠檢測到碰撞,可是不參與碰撞
      render: { fillStyle: "#060a19" },
      collisionFilter: { group: -1 }, // 參考reademe裏面的碰撞規則
    });
    this.setPhyPos();
  }
  set fx(value: number) {
    this.position.x = value;
    this.setPhyPos();
  }

  set fy(value: number) {
    this.position.y = value;
    this.setPhyPos();
  }

  setPhyPos() {
    Matter.Body.setPosition(this.phyBody, {
      x: (this.x + this.width / 2) * SS,
      y: (this.y + this.height / 2) * SS,
    });
  }
}
複製代碼

而後呢? 在首頁initBtn方法裏, 把西瓜剛體添加進去,而後把x,y改爲fxfy,這樣會同時設置視圖世界以及物理剛體的座標。

xigua.fx = 250;
xigua.fy = 400;
// @ts-ignore
this.composites.add(this.world, [xigua.phyBody]);
複製代碼

下面咱們要開啓物理引擎及碰撞了

step4: 物理引擎&碰撞檢測

王夢佳:」這是要開始切水果了嗎?「
熊東起:」是的呀,MM「

不出意外的話,能夠在屏幕上看到西瓜,以及刀光都有個白色的框框了。沒錯,那個就是物理世界畫的剛體。

西瓜刀光這倆物理剛體都已經繪製完成,下面就要讓他們進行碰撞。

首先在initBtn()加上, 開始遊戲頁,咱們已經把重力設置成0了,因此不用static了`

Matter.Body.setStatic(this.xigua.phyBody, false);
複製代碼

而後在createPhyWorld()中添加事件監聽

Matter.Events.on(this.engine, "collisionStart", this.onCollisionStart.bind(this));
複製代碼

添加對應的方法

/** 劃過開始按鈕 */
  doStart() {
    alert("開始遊戲");
  }
  /** * @description: 碰撞檢測 */
  onCollisionStart(e) {
    let pairs = e.pairs;
    if (this.xigua.phyBody.id === pairs[0].bodyA.id) {
      this.doStart();
    }
  }
複製代碼

鋼鐵碰撞會觸發,一個collisionStart回調,而後咱們判斷鋼鐵的id是否是西瓜的id就能夠了。因爲西瓜不能被碰跑,因此西瓜剛體加個屬性isSensor

isSensor: true, // 傳感器,能夠檢測到碰撞,可是不參與碰撞
複製代碼

你們能夠看到,西瓜的剛體已經掉落了。

而後這樣就能夠alert開始遊戲了 開始遊戲咱們須要作如下幾個處理

  • 西瓜切開,
  • 西瓜掉落
  • 清除當前頁面,進入新的
/** 劃過開始按鈕 */
  doStart() {
    this.engine.gravity.y = 1.2;
    /** 中止西瓜轉動 */
    FYGE.Tween.removeTweenSelf(this.TweenXigu);
    this.btnOut.visible = false;
    // this.xigua.doHalf();
    this.removeEvents();
    setTimeout(() => {
      // @ts-ignore
      this.composites.clear(this.world, true);
      Matter.Runner.stop(this.runner);
      Matter.Engine.clear(this.engine);
      this.parent.addChild(new GameScene());
      this.parent.removeChild(this);
    }, 1000);
  }
複製代碼

你們看上面代碼,看到有個doHalf。就是切西瓜了,目前先註釋。

視圖世界的西瓜掉落

你們剛纔都看到了物理剛體的掉落,而視圖世界的西瓜還在原地。 其實很簡單,就是每一幀去取物理剛體的座標,而後更改視圖世界西瓜的座標。 Fruit.ts constructor()

this.addEventListener(
      FYGE.Event.ADDED_TO_STAGE,
      () => {
        this.addEventListener(FYGE.Event.ENTER_FRAME, this.onFarm, this);
      },
      this
    );
複製代碼

Fruit.ts onFarm()

/** 根據物理剛體,更新當前的座標。 */
  private onFarm() {
    this.x = this.phyBody.position.x / SS - this.width / 2;
    this.y = this.phyBody.position.y / SS - this.height / 2;
  }
複製代碼

西瓜切開

這個更簡單,就是原來的圖隱藏,而後給這個西瓜添加兩個子節點。圖片提早準備好了。

Fruit.ts

/** 水果的兩半是兩個不一樣的圖。 */
  private half_pre: FYGE.Sprite;
  private half_next: FYGE.Sprite;
 /** 切成2半 */
   doHalf() {
    if (this.half_next || this.half_pre) return;
    this.half_pre = this.addChild(ADD_SPRITE(getRes(RES_MAP["fruitXigua" + "1"]), -5, 0));
    this.half_next = this.addChild(ADD_SPRITE(getRes(RES_MAP["fruitXigua" + "2"]), 5, 0));
    this.texture.valid = false;
    FYGE.Tween.get(this.half_pre).to({ x: GUtils.getRandom(-120, -80), rotation: GUtils.getRandom(-50, -30) }, GUtils.getRandom(2000, 4000));
    FYGE.Tween.get(this.half_next).to({ x: GUtils.getRandom(80, 120), rotation: GUtils.getRandom(30, 50) }, GUtils.getRandom(2000, 4000));
    Matter.Body.setStatic(this.phyBody, false);
  }
複製代碼

再看看,是否是就有了切開動畫了。 RES_MAP["fruitXigua" + "1"] 有人注意到這句話了麼,兩個字符串你擱着+啥+?

如今只有西瓜麼~,後面還有其餘水果,每一個水果的切開,都是水果名字+「1」

------------至此,開始頁就完成了。下面咱們進行遊戲頁。

step5: 遊戲頁

王夢佳:"熊GG,終於要開始遊戲了嘛,好期待啊"
熊東起:」想多啦,前面基本都已經實現了,遊戲頁也不過是簡單的隨機水果,記個分啥的「

如上他倆所述,遊戲頁已經沒有什麼新玩意了。

擴展水果

  • 水果的名字動態取
  • 給水果加一個是否死亡的狀態

Fruit.ts

public die: boolean = false;
  /** 水果名字 */
  private fName: FRUIT_NAME;

   constructor(fName: FRUIT_NAME = "fruitXigua") {
    super();
    this.fName = fName;
    /** 防止貼圖禁用後,後面沒辦法複用。 */
    this.texture = getRes(RES_MAP[this.fName]).clone();
   }
複製代碼

切開的事件裏面也換成真實水果圖片,以及die狀態 Fruit.ts doHalf

this.half_pre = this.addChild(ADD_SPRITE(getRes(RES_MAP[this.fName + "1"]), -5, 0));
this.half_next = this.addChild(ADD_SPRITE(getRes(RES_MAP[this.fName + "2"]), 5, 0));
this.die = true;
複製代碼

下面幾個以前都講過了。這裏直接貼出源碼

  • 建立物理世界
  • 生成水果
  • 隨機初始化水果
  • 給水果一個副作用力

完整的GameScene.ts

import Blade from "../components/blade";
import Fruit from "../components/Fruit";
import GameBg from "../components/GameBg";
import { RES_MAP, FRUIT_NAME, FRUIT_ARRAY, OVER_COUNT } from "../config/GameCfg";
import { ADD_TEXT, GUtils, _throttle } from "../config/GUtils";
import * as Matter from "matter-js";
import { StartScene } from "./StartScene";

export class GameScene extends FYGE.Container {
  private engine: Matter.Engine;
  private world: Matter.World;
  private render: Matter.Render;
  private runner: Matter.Runner;
  private composites: Matter.Composite;
  private gameover: boolean = false;
  /** 水果list */
  private fruits: Fruit[] = [];
  /** 水果的最大數量,會隨着分數增長難度 */
  private fruitMax: number = 4;

  /** 關卡 */
  private _lv: number = 1;
  /** 關卡文字 */
  private lvText: FYGE.TextField;
  private get lv(): number {
    return this._lv;
  }
  private set lv(v: number) {
    this._lv = v;
    this.lvText && (this.lvText.text = "第" + v + "關");
  }
  /** 刀光 */
  private blade: Blade;
  /** 分數文字 */
  private scoreText: FYGE.TextField;
  /** 分數 */
  private _score: number = 0;
  private get score(): number {
    return this._score;
  }
  private set score(v: number) {
    this._score = v;
    this.scoreText && (this.scoreText.text = "分數:" + v);
    FYGE.Tween.removeTweens(this.scoreText);
    FYGE.Tween.get(this.scoreText)
      .set({ scaleX: 1, scaleY: 1, anchorX: 30 })
      .to({ scaleX: 1.1, scaleY: 1.1, alpha: 1 }, 100)
      .to({ scaleX: 1, scaleY: 1 }, 50);
    if (v > 10) {
      this.lv = Math.ceil(v / 10);
    }
  }
  /** 丟掉的數量 */
  private _dieCount: number = 0;
  /** 文字 */
  private dieCountText: FYGE.TextField;
  private get dieCount(): number {
    return this._dieCount;
  }
  private set dieCount(v: number) {
    this._dieCount = v;
    this.dieCountText && (this.dieCountText.text = "丟失:" + v);
    // 丟的太多了,遊戲結束
    v >= OVER_COUNT &&
      (this.gameover = true) &&
      setTimeout(() => {
        this.gameOver();
      }, 17);
  }
  constructor() {
    super();
    this.addEventListener(
      FYGE.Event.ADDED_TO_STAGE,
      () => {
        this.addEvents();
        this.initGame();
      },
      this
    );
  }

  /** 初始化遊戲 */
  initGame() {
    this.addChild(new GameBg());

    this.blade = this.addChild(new Blade());

    this.createPhyWorld();
    /** 固然是生成水果了 */
    this.genneratorFruit();
    /** 關卡 */
    this.lvText = this.addChild(ADD_TEXT("第1關", 30, "#66ff00", 50, 50));
    /** 分數 */
    this.scoreText = this.addChild(ADD_TEXT("分數:0", 40, "#66ffff", this.stage.width / 2 - 50, 50));
    /** 丟失跑掉的水果 */
    this.dieCountText = this.addChild(ADD_TEXT("丟失:0", 30, "#ff3399", this.stage.width - 150, 50));
  }
  /** 建立物理世界 */
  private createPhyWorld() {
    const { Engine, Render, Runner, Composite, Bodies, World, Composites } = Matter;
    this.engine = Engine.create();
    this.world = this.engine.world;
    this.engine.gravity.y = 0.5;
    // 臨時渲染引擎,調試用
    var render = Render.create({
      element: document.body,
      engine: this.engine,
      options: {
        width: 750,
        height: 600,
      },
    });

    Render.run(render);

    /** 真正運行 */
    this.runner = Runner.create();
    Runner.run(this.runner, this.engine);
    // @ts-ignore
    this.composites = Composite;
    Matter.Events.on(this.engine, "collisionStart", this.onCollisionStart.bind(this));
  }
  /** 生成水果 */
  genneratorFruit() {
    if (this.gameover) return;
    while (this.fruits.length < this.fruitMax + this.lv) {
      this.randomFruit();
    }
  }

  /** 隨機水果 */
  randomFruit() {
    let index = Math.floor(Math.random() * FRUIT_ARRAY.length);
    let fruit = new Fruit(FRUIT_ARRAY[index]);

    fruit.fx = GUtils.getRandom(this.stage.width * 0.25, this.stage.width * 0.75);
    fruit.fy = this.stage.height;
    // @ts-ignore
    this.composites.add(this.world, [fruit.phyBody]);

    FYGE.Tween.get(fruit, { loop: true }).to({ rotation: 360 }, 4000);

    this.addChild(fruit);
    // timeout是 調試用的
    setTimeout(() => {
      if (!this.stage) return;
      Matter.Body.setStatic(fruit.phyBody, false);
      let sh = this.stage.height / 1334;
      Matter.Body.setVelocity(fruit.phyBody, { x: GUtils.getRandom(-3, 3), y: GUtils.getRandom(-15 * sh, -12 * sh) });
    }, GUtils.getRandom(0, 2000));
    this.fruits.push(fruit);
  }

  /** * @description: 碰撞檢測 */
  onCollisionStart(e) {
    let pairs = e.pairs;
    pairs.map((p) => {
      let needHalf = this.fruits.find((fruit) => fruit.phyBody?.id === p.bodyA?.id);
      !needHalf?.die && (this.score += 1) && needHalf?.doHalf();
    });
  }
  /** 鼠標移動 */
  private onMouseMove(e) {
    _throttle(this.blade.drawBlade(e), 50);
    /** 廢棄,原本是用的hitTestPoint來作的碰撞檢測。 */
    // this.blade.checkCollide(this.xigua, this.doStart.bind(this));
  }

  /** 監聽事件 */
  private addEvents() {
    this.stage.addEventListener(FYGE.MouseEvent.MOUSE_MOVE, this.onMouseMove, this);
    this.stage.addEventListener(FYGE.MouseEvent.MOUSE_UP, this.onMouseUp, this);
    this.addEventListener(FYGE.Event.ENTER_FRAME, this.onFarm, this);
  }

  /** 移除事件 */
  private removeEvents() {
    this.stage.removeEventListener(FYGE.MouseEvent.MOUSE_MOVE, this.onMouseMove, this);
    this.stage.removeEventListener(FYGE.MouseEvent.MOUSE_UP, this.onMouseUp, this);
    this.removeEventListener(FYGE.Event.ENTER_FRAME, this.onFarm, this);
  }

  private onMouseUp() {
    this.blade.reset();
  }

  private onFarm() {
    this.fruits.map((f, i) => {
      if (f.y > this.stage?.height) {
        console.log("你走", f.die);
        // 跑出去的水果若是還存活,就丟失+1
        if (!f.die) {
          this.dieCount += 1;
        }
        this.removeChild(f);
        this.fruits.splice(i, 1);
        this.genneratorFruit();
      }
    });
  }

  /** * @description: 遊戲結束 */
  private gameOver() {
    this.removeEvents();
    this.removeAllChildren();

    alert("遊戲結束");
    // @ts-ignore
    this.composites.clear(this.world, true);
    Matter.Runner.stop(this.runner);
    Matter.Engine.clear(this.engine);
    this.parent.addChild(new StartScene());
    this.parent.removeChild(this);
  }
}
複製代碼

這裏拓展下碰撞檢測的規則 好比水果不能和水果進行碰撞。 咱們直接使用collisionFiltergroup屬性。 若是更復雜的能夠參考下面

碰撞檢測規則

簡單的碰撞關係,直接設置group便可複雜的碰撞關係,能夠經過設置category和和mask值進行搭配,作出很高級的碰撞關係

Matter相互碰撞提供了collisionFilter屬性,支持三種屬性,分別是 group category mask

在兩個group相等的前提下

若是任意group大於零,則二者始終碰撞,好比你們都是1,這你們相互直接始終碰撞 若是任意group小於0,好比你們都是-1,則你們永遠也不碰撞,咱們的水果就是用的這個 除上述兩種狀況,則根據category和mask進行斷定

在兩個group不相等的前提下

category,mask斷定規則 category表明一個碰撞分類,其值可爲1,2,4,8...直到 2^31,每一個剛體設置一個值 mask爲碰撞集合(category集合),是category相與的結果值,好比接受2,4類型,其值爲6 a和b碰撞狀況是 a的mask必須包含b的category同時b的mask也必須包含a的category,即 (a.category & b.mask) !== 0 && (b.category & a.mask) !== 0

更多的規則就去找API吧。

總結

至此,簡單的切水果遊戲就完事了。 邏輯也很簡陋。僅做爲學習交流使用。

熊東起:「我好了, 你學fei了嗎」 王夢佳:「啥也不是~」

相關文章
相關標籤/搜索