You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Phaser刮刮乐:监测擦除像素占比的方法及现有实现问题排查

刮刮乐刮擦进度监测问题(Phaser)

问题描述

我想用Phaser开发刮刮乐游戏,需求是用户刮擦卡片覆盖层显示下方内容,当刮擦面积达到画布70%时自动完全显示内容。目前功能无法正常运行,尝试通过ImageData获取像素数组但返回全零数组,当前使用calculateScratchRatio函数实现进度计算,代码如下:

import BaseScene from "./BaseScene";

const SKY_IMAGE = "sky";
const SKY_IMAGE_BLACK = "skyblack";
const BOARD = "board";
const HEADER_ACT = "header_act";
const KEY_BRUSH = "brush";
const BGGAME = "bg-game";

export default class Scratch extends BaseScene {
  constructor(config) {
    super("Scratch", { ...config });
    this.config = config;
    this.isDown = false;
    this.renderTexture = null;
    this.brush = null;
    this.erasedPixels = 0;
    this.screenCenter = [config.width / 2, config.height / 2];
  }

  create() {
    super.create();
    this.cover = this.make.image({
      key: SKY_IMAGE_BLACK,
      add: false,
    });
    this.board = this.make.image({
      key: BOARD,
      add: false,
    });
    this.ScratchOff();
    this.add.image(...this.screenCenter, BOARD).setScale(0.7);
    console.log(this.board.getBounds());
    const headerinfo = this.add
      .image(this.screenCenter[0] - 160, 130, "header_act")
      .setScale(0.7);
    let helloWorld = this.add
      .text(0, 0, "Hello World")
      .setFont("20px Arial")
      .setColor("#ffffff");

    const container = this.add.container(headerinfo.x, headerinfo.y);
    container.add(helloWorld);
  }

  ScratchOff() {
    this.add
      .image(this.screenCenter[0] - 160, this.screenCenter[1], SKY_IMAGE)
      .setScale(0.7);

    this.cover.setOrigin(0, 0);

    const width = this.cover.width;
    const height = this.cover.height;
    console.log(width, height);

    const rt = this.add.renderTexture(
      this.screenCenter[0] - 160,
      this.screenCenter[1],
      width * 0.7,
      height * 0.71
    );
    this.isRenderTextureErased = false;
    this.erasureThreshold = 0.99;
    rt.setOrigin(0.5, 0.5);
    rt.draw(this.cover);

    rt.setInteractive();
    rt.on(Phaser.Input.Events.POINTER_DOWN, this.handlePointerDown, this);
    rt.on(Phaser.Input.Events.POINTER_MOVE, this.handlePointerMove, this);
    rt.on(Phaser.Input.Events.POINTER_UP, () => (this.isDown = false));

    this.brush = this.make.image({
      key: KEY_BRUSH,
      add: false,
    });

    this.renderTexture = rt;
  }

  handlePointerDown(pointer) {
    this.isDown = true;
    this.handlePointerMove(pointer);
  }

  handlePointerMove(pointer) {
    if (!this.isDown) {
      return;
    }
    const x = pointer.x - this.renderTexture.x + this.renderTexture.width * 0.5;
    const y =
      pointer.y - this.renderTexture.y + this.renderTexture.height * 0.5;
    this.renderTexture.erase(this.brush, x, y);
    const result = this.calculateScratchRatio(x, y);
    console.log("result", result);
  }

  calculateScratchRatio(x, y) {
    const texture = this.textures.get(SKY_IMAGE_BLACK);
    console.log(texture);
    if (!texture) {
      console.error(`Texture with key '${SKY_IMAGE_BLACK}' not found.`);
      return 0;
    }

    console.log(texture);
    const canvas = document.createElement("canvas");
    canvas.width = texture.source[0].width;
    console.log("canvas.width", canvas.width);
    canvas.height = texture.source[0].height;
    const context = canvas.getContext("2d");
    context.drawImage(texture.source[0].image, 0, 0);

    const imageData = context.getImageData(0, 0, canvas.width, canvas.height);

    const pixels = imageData.data;
    console.log(imageData, pixels);
    let erasedCount = 0;

    for (let i = 3; i < pixels.length; i += 4) {
      const alpha = pixels[i + 3];
      if (alpha < 128) {
        erasedCount++;
      }
    }

    const totalPixels = canvas.width * canvas.height;
    const scratchRatio = (erasedCount / totalPixels) * 100;

    return Math.round(scratchRatio);
  }
}

问题原因与修复

当前calculateScratchRatio函数的核心问题是:它读取的是原始纹理SKY_IMAGE_BLACK的像素数据,而不是用户刮擦后RenderTexture上的实时像素状态,所以永远无法检测到擦除操作带来的像素变化,自然返回错误结果。

修改后的calculateScratchRatio函数,应该直接从RenderTexture获取画布数据:

calculateScratchRatio() {
  // 获取RenderTexture对应的画布
  const canvas = this.renderTexture.getCanvas();
  if (!canvas) return 0;

  const context = canvas.getContext("2d");
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = imageData.data;
  
  let erasedCount = 0;
  const totalPixels = canvas.width * canvas.height;

  // 遍历像素,统计透明(alpha < 128)的像素数量
  for (let i = 3; i < pixels.length; i += 4) {
    if (pixels[i] < 128) { // 修正之前的索引错误
      erasedCount++;
    }
  }

  const scratchRatio = (erasedCount / totalPixels) * 100;
  const roundedRatio = Math.round(scratchRatio);

  // 达到70%时自动清除全部覆盖层
  if (roundedRatio >= 70 && !this.isRenderTextureErased) {
    this.isRenderTextureErased = true;
    this.renderTexture.clear();
  }

  return roundedRatio;
}

同时,在handlePointerMove中调用时,不需要传x/y参数:

const result = this.calculateScratchRatio();

其他监测刮擦进度的方法

  1. 累计擦除面积
    每次调用erase方法时,计算笔刷覆盖的近似面积(比如笔刷是圆形,按π*r²计算),累加总面积。当累加值达到RenderTexture总面积*0.7时,触发自动清除。这种方法无需遍历像素,性能大幅提升,适合大多数场景。

  2. 区域采样估算
    不遍历全部像素,而是在画布上随机选取多个采样点(比如200个),统计透明点的比例,以此估算整体刮擦进度。精度可通过调整采样点数量平衡,性能比全量遍历好很多。

  3. 使用BitmapData(Phaser旧版本)
    若使用Phaser 3.x之前的版本,可使用BitmapData替代RenderTexture,它提供了更直接的像素操作API,比如getPixelAlpha方法,可快速获取指定位置的透明度。

内容的提问来源于stack exchange,提问作者georgio bejjani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 13:30:57