Matter.js — 如何获取图像的尺寸来设置身体尺寸?

Matter.js — How to get the dimension of an image to set Bodies sizes?

我正在尝试以编程方式设置 matter.js 中链接体的宽度和高度。不幸的是,我只得到 0 作为值,我不确定为什么。我的猜测是图像加载速度不够快,无法提供这些值。如何在加载图像之前加载这些尺寸?

Pseudo-code

代码

var playA = Composites.stack(
  percentX(25) - assetSize / 2,
  percentY(25),
  1,
  6,
  5,
  5,
  function (x, y) {
    iA++;

    var imgWidth;
    var imgHeight;

    var img = new Image();
    img.src = String(design[iA]);

    var imgWidth = 0;
    var imgHeight = 0;

    img.onload = function a() {
      imgWidth = img.naturalWidth;
      imgHeight = img.naturalHeight;

      console.log(String(design[iA]), imgWidth, imgHeight);
    };
    console.log(String(design[iA]), imgHeight, imgWidth); // I can't access the values here.

    return Bodies.rectangle(x, y, imgWidth, imgHeight, {
      // collisionFilter: { group: group },
      friction: 1,
      render: {
        sprite: {
          texture: design[iA],
          xScale: (assetSize / 100) * 0.46,
          yScale: (assetSize / 100) * 0.46
        }
      }
    });
  }
);

Composites.chain(playA, 0.3, 0, -0.5, 0, {
  stiffness: 1,
  length: 10,
  render: { type: "line", visible: false }
});

如果您知道尺寸并且可以预先填充数组,则解决方案可能很简单,因为 Matter.js 加载给定 URL 字符串的图像,但需要注意的是引擎不会等待运行.

之前的负载

这是一个迭代数组中的 width/height 对并将这些属性传递到 rectangle 调用的最小示例,我将使用这些调用作为与您的用例匹配的示例的垫脚石.

const engine = Matter.Engine.create();
const render = Matter.Render.create({
  element: document.body,
  engine: engine,
  options: {
    width: 450,
    height: 250,
    wireframes: false, // required for images
  }
});
Matter.Render.run(render);

const runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);

const imgSizes = [[56, 48], [45, 50], [35, 50], [60, 63]];
const stack = Matter.Composites.stack(
  // xx, yy, columns, rows, columnGap, rowGap, cb
  150, 50, 4, 1, 0, 0,
  (x, y, i) => {
    const [w, h] = imgSizes[i];
    return Matter.Bodies.rectangle(x, y, w, h, {
      render: {
        sprite: {
          texture: `http://placekitten.com/${w}/${h}` 
        }
      }
    });
  }
);
Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
  stiffness: 0.75,
  length: 10,
  render: {type: "line", visible: true}
});

Matter.Composite.add(engine.world, [
  stack,
  Matter.Bodies.rectangle(225, 0, 450, 25, {
    isStatic: true
  }),
  Matter.Bodies.rectangle(450, 150, 25, 300, {
    isStatic: true
  }),
  Matter.Bodies.rectangle(0, 150, 25, 300, {
    isStatic: true
  }),
  Matter.Bodies.rectangle(225, 250, 450, 25, {
    isStatic: true
  })
]);

const mouse = Matter.Mouse.create(render.canvas);
const mouseConstraint = Matter.MouseConstraint.create(engine, {
  mouse: mouse,
  constraint: {
    stiffness: 0.2,
    render: {visible: true}
  }
});
Matter.Composite.add(engine.world, mouseConstraint);
render.mouse = mouse;
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>

现在,如果您需要使用 onload 加载图像并使用它们的尺寸,您将需要使用 promises 或将依赖于这些图像的 all 代码放入规范 How do I return the response from an asynchronous call?.

中描述的 onload 回调序列

失败的模式是:

const getSomethingAsync = cb => setTimeout(() => cb("something"), 0);

let data = null;
getSomethingAsync(result => {
  data = result;
  console.log("this runs last");
});
console.log(data); // guaranteed to be null, not "something"
// more logic that is supposed to depend on data

修复方法是:

const getSomethingAsync = cb => setTimeout(() => cb("something"), 0);

getSomethingAsync(data => {
  console.log(data);
  // logic that depends on the data from `getSomethingAsync`
});

console.log("this will run first");
// logic that doesn't depend on data from `getSomethingAsync`

由于您要兼顾多个 onload,您可以承诺 onload 使它们更易于使用。我有几个这样做的例子 and 与 matter.js 无关。

下面是一个使用 promises 加载适用于您的一般问题的图像的示例。同样,我将使用我自己的代码,以便它可以运行和重现,但该模式应该很容易外推到您的项目中。

想法是首先使用一系列承诺加载图像,这些承诺在 onload 处理程序触发时解决,然后使用 Promise.all 链接运行 MJS 初始化程序的 then仅在加载所有图像时回调。然后回调中的 matter.js 代码可以访问宽度和高度。

附带的好处是,这可确保在 MJS 运行时加载图像。

const initializeMJS = images => {
  const engine = Matter.Engine.create();
  const render = Matter.Render.create({
    element: document.body,
    engine: engine,
    options: {
      width: 450,
      height: 250,
      wireframes: false, // required for images
    }
  });
  Matter.Render.run(render);

  const runner = Matter.Runner.create();
  Matter.Runner.run(runner, engine);

  const stack = Matter.Composites.stack(
    // xx, yy, columns, rows, columnGap, rowGap, cb
    150, 50, 4, 1, 0, 0,
    (x, y, i) => {
      const {width: w, height: h} = images[i];
      return Matter.Bodies.rectangle(x, y, w, h, {
        render: {
          sprite: {
            texture: images[i].src
          }
        }
      });
    }
  );
  Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
    stiffness: 0.75,
    length: 10,
    render: {type: "line", visible: true}
  });

  Matter.Composite.add(engine.world, [
    stack,
    Matter.Bodies.rectangle(225, 0, 450, 25, {
      isStatic: true
    }),
    Matter.Bodies.rectangle(450, 150, 25, 300, {
      isStatic: true
    }),
    Matter.Bodies.rectangle(0, 150, 25, 300, {
      isStatic: true
    }),
    Matter.Bodies.rectangle(225, 250, 450, 25, {
      isStatic: true
    })
  ]);

  const mouse = Matter.Mouse.create(render.canvas);
  const mouseConstraint = Matter.MouseConstraint.create(engine, {
    mouse: mouse,
    constraint: {
      stiffness: 0.2,
      render: {visible: true}
    }
  });
  Matter.Composite.add(engine.world, mouseConstraint);
  render.mouse = mouse;
};

const imageSizes = [[56, 48], [45, 50], [35, 50], [60, 63]];
const imageURLs = imageSizes.map(([w, h]) =>
  `http://placekitten.com/${w}/${h}`
);

Promise.all(imageURLs.map(e =>
    new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = e;
    })
  ))
  .then(initializeMJS)
;
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>