我想测试一个特定的物质体是否是圆形,如:
const compounds = Matter.Composite.allBodies(engine.world)
compounds.forEach(compound => compound.parts.forEach(part => {
const isCircle = ???
if (isCircle) console.log(part.id, 'is a circle')
else console.log(part.id, 'is not a circle')
})
我找不到官方方法来测试物质体是否被创建为圆形。如何测试主体是否是使用
new Matter.Body.Circle
与其他主体构造函数创建的?
您可以
console.log(a_circle)
并检查是否有可用来识别圆的东西。
我想你可以检查一下 a_circle.circleRadius
或 a_circle.label=='Circle Body'
编辑:在发布此内容之前我已经查看了源代码。这是一个安全的赌注(目前还没有文档),因为您可以看到否则只是一个多边形。
Matter.Bodies.circle = function(x, y, radius, options, maxSides) {
options = options || {};
var circle = {
label: 'Circle Body',
circleRadius: radius
};
// approximate circles with polygons until true circles implemented in SAT
maxSides = maxSides || 25;
var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));
// optimisation: always use even number of sides (half the number of unique axes)
if (sides % 2 === 1)
sides += 1;
return Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options));
}
另一个答案建议寻找特定于圆的属性。一个问题是,这些可能会在未来的 Matter.js 版本中发生变化。此外,它无法生成可读、直观的代码,并且当其他主体类型意外包含某个属性时,可能会导致令人惊讶的错误。
更好的是使用内部label
属性(该答案中也建议),该属性应该稳定并默认为看似有用的
"Rectangle Body"
和
"Circle Body"
。对于简单的用例,这是可行的。由于可以将标签设置为对象以在主体上存储任意自定义数据,因此很容易进一步使用标签来处理几乎所有内容。但是,我通常会忽略标签。原因是它将太多的客户端逻辑推入物理库中,而该库并不是真正为实体管理而设计的。此外,这些方法(标签或特定于主体的属性)都涉及迭代所有主体以过滤出您感兴趣的类型。
尽管没有提供有关该应用程序的上下文,但必须调用
allBodies
通常似乎是一种潜在的反模式。也许是时候考虑重新设计了,这样您就不必这样做了。如果你有 5 个圆圈和 500 个其他物体怎么办?在每一帧上递归地迭代所有 500 只是为了找到 5,这是对资源的巨大浪费,无法实现本应简单高效的目标。我首选的实体管理解决方案是在创建时简单地跟踪每种类型,将它们放入根据应用程序特定需求进行调整的数据结构中。
例如,以下脚本展示了一种通过将身体呈现为预建
types
地图的关键来有效确定身体类型的方法。
const engine = Matter.Engine.create();
engine.gravity.y = 0; // enable top-down
const map = {width: 300, height: 300};
const runner = Matter.Runner.create();
const render = Matter.Render.create({
element: document.querySelector("#container"),
engine,
options: {...map, wireframes: false},
});
const rnd = n => ~~(Math.random() * n);
const rects = [...Array(20)].map(() => Matter.Bodies.rectangle(
rnd(map.width), // x
rnd(map.height), // y
rnd(10) + 15, // w
rnd(10) + 15, // h
{
angle: rnd(Math.PI * 2),
render: {fillStyle: "pink"}
}
));
const circles = [...Array(20)].map(() => Matter.Bodies.circle(
rnd(map.width), // x
rnd(map.height), // y
rnd(5) + 10, // r
{render: {fillStyle: "red"}}
));
const walls = [
Matter.Bodies.rectangle(
0, map.height / 2, 20, map.height, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
Matter.Bodies.rectangle(
map.width / 2, 0, map.width, 20, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
Matter.Bodies.rectangle(
map.width, map.height / 2, 20, map.height, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
Matter.Bodies.rectangle(
map.width / 2, map.height, map.width, 20, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
];
const rectangle = Symbol("rectangle");
const circle = Symbol("circle");
const wall = Symbol("wall");
const types = new Map([
...rects.map(e => [e, rectangle]),
...circles.map(e => [e, circle]),
...walls.map(e => [e, wall]),
]);
const bodies = [...types.keys()];
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.querySelector("#container")}
);
Matter.Composite.add(engine.world, [
...bodies, mouseConstraint
]);
Matter.Events.on(runner, "tick", event => {
const underMouse = Matter.Query.point(
bodies,
mouseConstraint.mouse.position
);
if (underMouse.length) {
const descriptions = underMouse.map(e =>
types.get(e).description
);
document.querySelector("#type-hover").textContent = `
${descriptions.join(", ")} hovered
`;
}
else {
document.querySelector("#type-hover").textContent = `
[hover a body]
`;
}
if (mouseConstraint.body) {
document.querySelector("#type-click").textContent = `
${types.get(mouseConstraint.body).description} selected
`;
}
else {
document.querySelector("#type-click").textContent = `
[click and drag a body]
`;
}
});
Matter.Render.run(render);
Matter.Runner.run(runner, engine);
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"></script>
<h3 id="type-click">[click and drag a body]</h3>
<h3 id="type-hover">[hover a body]</h3>
<div id="container"></div>
如果创建和销毁实体可以动态发生,则需要编写一个函数来处理数据结构簿记。
另一种可能对某些应用程序有效的方法是拥有一些特定于类型的集或地图。这使您可以快速访问特定类型的所有实体。一旦您开始将主体组合为自定义类的属性,这将变得特别有用。
没有理由不能同时拥有这两种结构——反向查找给出给定 MJS 主体的类型或自定义类,以及包含对特定类型的所有实体或 MJS 主体的引用的结构。
反向查找可以启用诸如“单击时,根据其类型对特定 MJS 主体执行操作”或“单击时,找到与此 MJS 主体关联的自定义类/模型”之类的逻辑,而集合支持诸如“销毁”之类的逻辑所有敌人”。
理想情况下,代码不应该进行太多类型检查。通过正确的 OOP 设计,您可以实现能够正确响应方法的类,无论其类型如何。例如,如果您有
Enemy
和
Ally
类,每个类都响应单击,则可以在每个类中创建一个名为
handleClick
的方法。这允许您使用像
clickedEntity.handleClick();
这样的代码,而不必知道
clickedEntity
是
Enemy
还是
Ally
,从而完全避免了“获取类型”操作的需要。