1

so im trying to create a scratch card game where the user need to scratch the card to reveal what's underneath it. i want to check whenever the user has scratched 70% of the canvas to reveal it all. i am trying to get this to work using phaser but it's not working.

i have tried to calculate the imagedata to get the array of pixels but it's returning an array of zero. i am using the calculateScratchRatio function in the code below.

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); //, width * 0.5, height * 0.5)

    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);
  }
}

  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Aug 14 '23 at 19:22

1 Answers1

0

There is alot going on in your code, and it is hard to get it to work.
It is always better to post a mini runable code, like mentioned here.

Nevertheless a easy and fast solution would be to use canvasTexture, to create the cover texture, like this you can directly access the context from this object.

Here a short Demo, how I would do it:
(Based on the concept of this this answer)

document.body.style = 'margin:0;';
 
class ScratchScene extends Phaser.Scene {
  constructor() {
    super('ScratchScene');
  }
  
  preload(){
    this.load.image('coverImage', 'https://placehold.co/200x50/ff0000/FFFFFF.png?text=coverImage&font=montserrat');
  }
  
  create(){
    
    let coverImage = this.textures.get('coverImage').getSourceImage()
    this.coverHelperCanvas = this.textures.createCanvas('coverTexture', 200, 50)
    this.coverHelperCanvas.draw(0, 0, coverImage)
    this.coverHelperCanvas.context.globalCompositeOperation = 'destination-out'
      
    this.precent = this.add.text(10 , 10, '' );

    this.add.text( config.width/2, config.height / 2, 'YOU WON')
      .setOrigin(.5)
      .setFontSize(20);
    
    let cover = this.add.image(config.width/2, config.height / 2, 'coverTexture')
        .setInteractive();
      
    cover.on('pointermove', this.clearCover, this);
    
    // Add timer to check
    this.checkTimer = this.time.addEvent({delay: 250, loop: true, callback: this.checkPercent, callbackScope: this })
  }
  
  checkPercent(){
      let full = 200 * 50;
      let { data } =  this.coverHelperCanvas.context.getImageData(0, 0, 200, 50);
      let current = data.filter((v,i) => ((i + 1) % 4 == 0) && v > 0).length;
      let percent = (current  / full * 100);
      this.precent.setText(`Cover Percent: ${ percent.toFixed(2)}%` );
      
      if(percent < 50){
          this.coverHelperCanvas.context.fillRect(0, 0, 200, 50);
          this.coverHelperCanvas.update();
          this.checkTimer.remove();
      }
  }
  
  clearCover(e, x, y){
    let radius = 10;
    this.coverHelperCanvas.context.beginPath()
    this.coverHelperCanvas.context.arc(x, y, radius, 0, Math.PI * 2, false)
    this.coverHelperCanvas.context.fill();
    this.coverHelperCanvas.update();
    //this.checkPercent();
  }
}

var config = {
    type: Phaser.AUTO,
    width: 536,
    height: 163,
    scene: [ScratchScene]
}; 

new Phaser.Game(config);
console.clear();
<script src="https://cdn.jsdelivr.net/npm/phaser/dist/phaser.min.js"></script>
winner_joiner
  • 12,173
  • 4
  • 36
  • 61
  • how can i use this method if i want to scratch an image rather than a color – georgio bejjani Aug 14 '23 at 12:25
  • what should i do to make all the cover go away when i reach a specific percentage – georgio bejjani Aug 16 '23 at 09:36
  • is it better to use the destroy method or the clearRect one? – georgio bejjani Aug 16 '23 at 09:50
  • if (percentage <= 50) { this.coverHelperCanvas.context.clearRect(0, 0, this.coverImage.width, this.coverImage.height); this.coverHelperCanvas.update(); } i am using this method – georgio bejjani Aug 16 '23 at 09:50
  • It doesn't matter if you use `destroy` or update the canvas. **If** you will/want to reuse the image (let the player scratch again), the your version is the better choice – winner_joiner Aug 16 '23 at 10:02
  • okay deal, i have an issue, the scratching seems to be slow. and i think it's because the canva is updating a lot. do you have any idea how can i limit the updates and make the scratching experience run smoothly – georgio bejjani Aug 16 '23 at 10:08
  • Well instead of checking all the time in the move event, you could check the percentage on a Time Interval. that would improve the performance. [link to the timer documentation](https://photonstorm.github.io/phaser3-docs/Phaser.Time.TimerEvent.html#remove__anchor) I updated my answer – winner_joiner Aug 16 '23 at 10:37
  • how can make the cover reappear if like i want the player to play again ? – georgio bejjani Aug 21 '23 at 10:46