skip to Main Content

I am programming an OOP 2d canvas game, and I am seeing some weird behaviour with my code. When inside "e", the workspace, there is two or more of the same object but nothing else, ctx.resetTransform somehow does not work, but the moment I create a new object of a different type in console, ie. e.create('bullet'), the bug goes away. Does anyone know why that is?

var c = document.querySelector("canvas");
ctx = c.getContext("2d", {alpha: false});

const objs = {
  flower: {
    name: "flower",
    rot: 45,
    vel: {x: 0, y: 0},
    pos: {x: 50, y: 100},
    size: {x: 100, y: 100},
    sprite: [
      [0.4, 0.5, 0.2, 0.5, "green"],
      [0.3, 0.1, 0.4, 0.4, "pink"],
    ],
  },
  bullet: {
    name: "bullet",
    rot: 0,
    vel: {x: 0, y: 0},
    pos: {x: 50, y: 100},
    size: {x: 5, y: 10},
    sprite: [[0, 0, 1, 1, "black"]],
  },
};

var e = [];

e.create = function (cr) {
  crr = structuredClone(objs[cr]);
  e[e.length] = crr;
  return crr;
};

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function render() {
  ctx.clearRect(0, 0, c.width, c.height);

  for (i = 0; i < e.length; i++) {
    var obj = e[i];

    for (ii = 0; ii < obj.sprite.length; ii++) {
      ctx.resetTransform();
      ctx.translate(
        obj.pos.x + obj.size.x,
        obj.pos.y + obj.size.y
      );
      ctx.rotate((obj.rot / 180) * Math.PI);
      ctx.translate(
        -(obj.pos.x + obj.size.x),
        -(obj.pos.y + obj.size.y)
      );
      ctx.fillStyle = obj.sprite[ii][4];
      ctx.fillRect(
        obj.pos.x +
          obj.sprite[ii][0] * obj.size.x -
          obj.size.x / 2,
        obj.pos.y +
          obj.sprite[ii][1] * obj.size.y -
          obj.size.y / 2,
        obj.sprite[ii][2] * obj.size.x,
        obj.sprite[ii][3] * obj.size.y
      );
    }
  }
}

setInterval(() => {
  render();
}, 50);

setInterval(() => {
  e[0].pos.x = Math.random() * c.width;
}, 50);

e.create("flower");
e.create("flower");
<canvas height="1080" width="1920"></canvas>

2

Answers


  1. The issue with ctx.resetTransform() in your 2D canvas game seems to be unusual. Normally, this function should reset the current transform to the identity matrix regardless of the objects in the workspace. However, based on your description, it appears that there is a specific behavior that depends on the state or composition of your e array.

    <canvas height="1080" width="1920"></canvas>
    <script>
    var c = document.querySelector("canvas");
    var ctx = c.getContext("2d", {alpha: false});
    const objs = {
      flower: {
        name: "flower",
        rot: 45,
        vel: {x: 0, y: 0},
        pos: {x: 50, y: 100},
        size: {x: 100, y: 100},
        sprite: [
          [0.4, 0.5, 0.2, 0.5, "green"],
          [0.3, 0.1, 0.4, 0.4, "pink"],
        ],
      },
      bullet: {
        name: "bullet",
        rot: 0,
        vel: {x: 0, y: 0},
        pos: {x: 50, y: 100},
        size: {x: 5, y: 10},
        sprite: [[0, 0, 1, 1, "black"]],
      },
    };
    var e = [];
    
    e.create = function (cr) {
      var crr = structuredClone(objs[cr]);
      e.push(crr);
      return crr;
    };
    
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function render() {
      console.log('Render start');
      ctx.clearRect(0, 0, c.width, c.height);
      e.forEach(obj => {
        obj.sprite.forEach(sprite => {
          ctx.resetTransform();
          ctx.translate(obj.pos.x + obj.size.x, obj.pos.y + obj.size.y);
          ctx.rotate((obj.rot / 180) * Math.PI);
          ctx.translate(-(obj.pos.x + obj.size.x), -(obj.pos.y + obj.size.y));
          ctx.fillStyle = sprite[4];
          ctx.fillRect(
            obj.pos.x + sprite[0] * obj.size.x - obj.size.x / 2,
            obj.pos.y + sprite[1] * obj.size.y - obj.size.y / 2,
            sprite[2] * obj.size.x,
            sprite[3] * obj.size.y
          );
        });
      });
      console.log('Render end');
    }
    
    setInterval(() => {
      render();
    }, 50);
    
    setInterval(() => {
      e[0].pos.x = Math.random() * c.width;
    }, 50);
    
    e.create("flower");
    e.create("flower");
    </script>
    
    Login or Signup to reply.
  2. It’s not obvious but with the CanvasRenderingContext2D’s resetTransform() method the point of time actually calling it is important. To cite the mdn web docs :

    Whenever you’re done drawing transformed shapes, you should call resetTransform() before rendering anything else.

    So the remedy is pretty easy – move ctx.resetTransform(); from the beginning to the end of your for-loop – otherwise it might result in the ‘ghosting images’ you’ve experienced.

    var c = document.querySelector("canvas");
    ctx = c.getContext("2d", {alpha: false});
    
    const objs = {
      flower: {
        name: "flower",
        rot: 45,
        vel: {x: 0, y: 0},
        pos: {x: 50, y: 100},
        size: {x: 100, y: 100},
        sprite: [
          [0.4, 0.5, 0.2, 0.5, "green"],
          [0.3, 0.1, 0.4, 0.4, "pink"],
        ],
      },
      bullet: {
        name: "bullet",
        rot: 0,
        vel: {x: 0, y: 0},
        pos: {x: 50, y: 100},
        size: {x: 5, y: 10},
        sprite: [[0, 0, 1, 1, "black"]],
      },
    };
    
    var e = [];
    
    e.create = function (cr) {
      crr = JSON.parse(JSON.stringify(objs[cr]));
      e.push(crr)
     
    };
    
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function render() {
      ctx.clearRect(0, 0, c.width, c.height);
      for (i = 0; i < e.length; i++) {
        var obj = e[i];
    
        for (ii = 0; ii < obj.sprite.length; ii++) {
          ctx.translate(
            obj.pos.x + obj.size.x,
            obj.pos.y + obj.size.y
          );
          ctx.rotate((obj.rot / 180) * Math.PI);
          ctx.translate(
            -(obj.pos.x + obj.size.x),
            -(obj.pos.y + obj.size.y)
          );
          ctx.fillStyle = obj.sprite[ii][4];
          ctx.fillRect(
            obj.pos.x +
              obj.sprite[ii][0] * obj.size.x -
              obj.size.x / 2,
            obj.pos.y +
              obj.sprite[ii][1] * obj.size.y -
              obj.size.y / 2,
            obj.sprite[ii][2] * obj.size.x,
            obj.sprite[ii][3] * obj.size.y
          );
         
           ctx.resetTransform();
        }
      }
    }
    
    
    
    e.create("flower");
    e.create("flower");
    
    setInterval(() => {
       e[0].pos.x = Math.random() * c.width;
      render();
    }, 590);
    <canvas height="300" width="600"></canvas>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search