React-native-game-engine + matter.js 物体的动态比例
React-native-game-engine + matter.js dynamic scale of bodies
我正在尝试构建一个带有圆形主体(气泡)的屏幕。气泡应该能够以一定的间隔改变比例,例如每 3 秒和随机值(从 1 到 3)。刻度应该改变气泡的大小及其质量。
通话中
Matter.Body.scale
没有任何效果。气泡保持不变。也许是因为我没有使用带有纹理的精灵,而是使用 Game-Engine 渲染器作为 React.PureComponent 的实体。不确定
气泡节点:
export interface BubbleProps {
body: Matter.Body;
item: Item;
}
class BubbleNode extends React.Component<BubbleProps> {
render() {
const {body, item} = this.props;
const x = body.position.x - body.circleRadius;
const y = body.position.y - body.circleRadius;
const style: ViewStyle = {
position: 'absolute',
left: x,
top: y,
width: body.circleRadius * 2,
height: body.circleRadius * 2,
backgroundColor: item.color,
borderRadius: body.circleRadius,
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
padding: 3,
};
return <View style={style}>{item.content}</View>;
}
}
气泡实体:
const body = Matter.Bodies.circle(
randomX,
randomY,
RADIUS, //default radius 25
{
mass: 30,
}
);
return {
body,
item,
renderer: BubbleNode,
};
更新方法:
// find out if the entity was already added to the world
if (Object.prototype.hasOwnProperty.call(entities, id)) {
//update scale
const scale = bubble.item.scale
Matter.Body.scale(bubble.body, scale, scale)
} else {
//add new entity
entities[id] = bubble;
Matter.World.add(physics.world, [bubble.body]);
}
此外,我需要使缩放平滑
编辑:
有趣的事情。我能够使用 Matter.Body.scale 缩放气泡,但就在添加到世界之前。请问有没有方法可以在添加到世界后更新body
最终编辑
这是我用来设置世界并为 GameEngine 框架指定实体的方法
private setupWorld = (layout: LayoutRectangle) => {
const engine = Matter.Engine.create({enableSleeping: false});
const world = engine.world;
world.gravity.x = 0;
world.gravity.y = 0;
//center gravity body
Matter.use(MatterAttractors);
var attractiveBody = Matter.Bodies.circle(
layout.width / 2,
layout.height / 2,
1,
{
isSensor: true,
plugin: {
attractors: [centerGravity],
},
},
);
Matter.World.add(world, attractiveBody);
//walls
const wallThickness = 5;
let floor = Matter.Bodies.rectangle(
layout.width / 2,
layout.height - wallThickness / 2,
layout.width,
wallThickness,
{isStatic: true},
);
let ceiling = Matter.Bodies.rectangle(
layout.width / 2,
wallThickness / 2,
layout.width,
wallThickness,
{isStatic: true},
);
let left = Matter.Bodies.rectangle(
wallThickness / 2,
layout.height / 2,
wallThickness,
layout.height,
{isStatic: true},
);
let right = Matter.Bodies.rectangle(
layout.width - wallThickness / 2,
layout.height / 2,
wallThickness,
layout.height,
{isStatic: true},
);
Matter.World.add(world, [floor, ceiling, left, right]);
//basic entitites
this.entities = {
physics: {engine, world},
floor: {body: floor, color: 'green', renderer: Wall},
ceiling: {body: ceiling, color: 'green', renderer: Wall},
left: {body: left, color: 'green', renderer: Wall},
right: {body: right, color: 'green', renderer: Wall},
};
};
这里是将由 parent 具有一定间隔
的组件触发的方法
public updateNodes = (items: Item[]) => {
if (!this.state.mounted || !this.entities || !this.entities.physics || !this.layout) {
console.log('Missiing required data');
return;
}
const layout = this.layout
const entities = this.entities
const bubbles: BubbleEntity[] = items.map((item) => {
const randomX = randomPositionValue(layout.width);
const randomY = randomPositionValue(layout.height);
const body = Matter.Bodies.circle(
randomX,
randomY,
RADIUS, {
mass: 30,
}
);
body.label = item.id
return {
body,
item,
renderer: BubbleNode,
};
});
const physics = this.entities.physics as PhysicsEntity;
const allBodies = Matter.Composite.allBodies(physics.world)
bubbles.forEach((bubble) => {
//update existing or add new
const id = `bubble#${bubble.item.id}`;
if (Object.prototype.hasOwnProperty.call(entities, id)) {
//do physical node update here
//update scale and mass
const scale = bubble.item.scale
console.log('Updating item', id, scale);
//right her there used to be an issue because I used **bubble.body** which was not a correct reference to the world's body.
//so when I started to use allBodies array to find a proper reference of the body, everything started to work
let body = allBodies.find(item => item.label === bubble.item.id)
if (!!body) {
const scaledRadius = RADIUS*scale
const current = body.circleRadius || RADIUS
const scaleValue = scaledRadius/current
Matter.Body.scale(body, scaleValue, scaleValue)
}else{
console.warn('Physycal body not found, while the entity does exist');
}
} else {
console.log('Adding entity to the world');
entities[id] = bubble;
Matter.World.add(physics.world, [bubble.body]);
}
});
this.entities = entities
};
将来,我将改进该代码,我将为 body 使用一些变量,并将创建一个 matter.js 插件,使我能够扩展 [=54] =] 顺利而不是即时的,因为它现在可以工作。此外,上面的方法需要一些干净、简短的实现,而不是我试图让它工作的垃圾
您的示例并不完整;目前尚不清楚 MJS 引擎如何(或是否)运行ning。第一步是通过调用 Matter.Engine.update(engine);
in a requestAnimationFrame
循环来确保您有一个实际的渲染循环。
React 在这里似乎并不重要。它不应该影响结果,因为 MJS 引擎中每个 body 的 x/y 坐标和半径被提取并传递给视图组件,因此数据在一个方向上流动。我将在本示例的其余部分中省略它,但是一旦您对 MJS 端的工作感到满意,重新引入它应该很容易。
在 MJS 中缩放 body 的方法是调用 Matter.body.scale(body, scaleX, scaleY)
。此函数重新计算其他物理属性,例如 body.
的质量
有一个 annoying caveat 这个函数:它不是将绝对比例设置为 JS canvas 上下文或 CSS 转换可能,而是设置一个 相对规模。这意味着每次调用此函数都会更改未来调用的基线缩放比例。这样做的问题是舍入误差会累积并且会发生漂移。这也使得应用自定义补间变得困难。
解决方法可能取决于您希望实现的实际动画是什么(甚至可能不是必需的),因此除了建议编写与半径相关的逻辑外,我将避免规定任何过于具体的内容参考点以确保它保持在范围内。其他解决方法包括 re-creating 和每帧缩放圆圈。
使用圈子时的另一个陷阱是意识到 MJS circles are n-sided polygons, so small circles lose resolution when scaled up. Again, this is use-case dependent, but you may wish to create a Bodies.polygon
with more sides than would be created by Bodies.circle
如果您遇到异常行为。
即是说,这是一个最小的、完整的原始缩放示例,它向您展示了如何 运行 动画循环并调用 scale
以随时间动态调整它。将其视为概念证明,需要进行调整才能适用于您的用例(无论是什么)。
const engine = Matter.Engine.create();
const circles = [...Array(15)].map((_, i) => {
const elem = document.createElement("div");
elem.classList.add("circle");
document.body.append(elem);
const body = Matter.Bodies.circle(
// x, y, radius
10 * i + 60, 0, Math.random() * 5 + 20
);
return {
elem,
body,
offset: i,
scaleAmt: 0.05,
speed: 0.25,
};
});
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.body}
);
const walls = [
Matter.Bodies.rectangle(
// x, y, width, height
innerWidth / 2, 0, innerWidth, 40, {isStatic: true}
),
Matter.Bodies.rectangle(
innerWidth / 2, 180, innerWidth, 40, {isStatic: true}
),
Matter.Bodies.rectangle(
0, innerHeight / 2, 40, innerHeight, {isStatic: true}
),
Matter.Bodies.rectangle(
300, innerHeight / 2, 40, innerHeight, {isStatic: true}
),
];
Matter.Composite.add(
engine.world,
[mouseConstraint, ...walls, ...circles.map(e => e.body)]
);
/*
naive method to mitigate scaling drift over time.
a better approach would be to scale proportional to radius.
*/
const baseScale = 1.00063;
(function update() {
requestAnimationFrame(update);
circles.forEach(e => {
const {body, elem} = e;
const {x, y} = body.position;
const {circleRadius: radius} = body;
const scale = baseScale + e.scaleAmt * Math.sin(e.offset);
Matter.Body.scale(body, scale, scale);
e.offset += e.speed;
elem.style.top = `${y - radius / 2}px`;
elem.style.left = `${x - radius / 2}px`;
elem.style.width = `${2 * radius}px`;
elem.style.height = `${2 * radius}px`;
elem.style.transform = `rotate(${body.angle}rad)`;
});
Matter.Engine.update(engine);
})();
* {
margin: 0;
padding: 0;
}
body, html {
height: 100%;
}
.circle {
border-radius: 50%;
position: absolute;
cursor: move;
background: rgb(23, 0, 36, 1);
background: linear-gradient(
90deg,
rgba(23, 0, 36, 1) 0%,
rgba(0, 212, 255, 1) 100%
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
我正在尝试构建一个带有圆形主体(气泡)的屏幕。气泡应该能够以一定的间隔改变比例,例如每 3 秒和随机值(从 1 到 3)。刻度应该改变气泡的大小及其质量。
通话中
Matter.Body.scale
没有任何效果。气泡保持不变。也许是因为我没有使用带有纹理的精灵,而是使用 Game-Engine 渲染器作为 React.PureComponent 的实体。不确定
气泡节点:
export interface BubbleProps {
body: Matter.Body;
item: Item;
}
class BubbleNode extends React.Component<BubbleProps> {
render() {
const {body, item} = this.props;
const x = body.position.x - body.circleRadius;
const y = body.position.y - body.circleRadius;
const style: ViewStyle = {
position: 'absolute',
left: x,
top: y,
width: body.circleRadius * 2,
height: body.circleRadius * 2,
backgroundColor: item.color,
borderRadius: body.circleRadius,
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
padding: 3,
};
return <View style={style}>{item.content}</View>;
}
}
气泡实体:
const body = Matter.Bodies.circle(
randomX,
randomY,
RADIUS, //default radius 25
{
mass: 30,
}
);
return {
body,
item,
renderer: BubbleNode,
};
更新方法:
// find out if the entity was already added to the world
if (Object.prototype.hasOwnProperty.call(entities, id)) {
//update scale
const scale = bubble.item.scale
Matter.Body.scale(bubble.body, scale, scale)
} else {
//add new entity
entities[id] = bubble;
Matter.World.add(physics.world, [bubble.body]);
}
此外,我需要使缩放平滑
编辑:
有趣的事情。我能够使用 Matter.Body.scale 缩放气泡,但就在添加到世界之前。请问有没有方法可以在添加到世界后更新body
最终编辑
这是我用来设置世界并为 GameEngine 框架指定实体的方法
private setupWorld = (layout: LayoutRectangle) => {
const engine = Matter.Engine.create({enableSleeping: false});
const world = engine.world;
world.gravity.x = 0;
world.gravity.y = 0;
//center gravity body
Matter.use(MatterAttractors);
var attractiveBody = Matter.Bodies.circle(
layout.width / 2,
layout.height / 2,
1,
{
isSensor: true,
plugin: {
attractors: [centerGravity],
},
},
);
Matter.World.add(world, attractiveBody);
//walls
const wallThickness = 5;
let floor = Matter.Bodies.rectangle(
layout.width / 2,
layout.height - wallThickness / 2,
layout.width,
wallThickness,
{isStatic: true},
);
let ceiling = Matter.Bodies.rectangle(
layout.width / 2,
wallThickness / 2,
layout.width,
wallThickness,
{isStatic: true},
);
let left = Matter.Bodies.rectangle(
wallThickness / 2,
layout.height / 2,
wallThickness,
layout.height,
{isStatic: true},
);
let right = Matter.Bodies.rectangle(
layout.width - wallThickness / 2,
layout.height / 2,
wallThickness,
layout.height,
{isStatic: true},
);
Matter.World.add(world, [floor, ceiling, left, right]);
//basic entitites
this.entities = {
physics: {engine, world},
floor: {body: floor, color: 'green', renderer: Wall},
ceiling: {body: ceiling, color: 'green', renderer: Wall},
left: {body: left, color: 'green', renderer: Wall},
right: {body: right, color: 'green', renderer: Wall},
};
};
这里是将由 parent 具有一定间隔
的组件触发的方法public updateNodes = (items: Item[]) => {
if (!this.state.mounted || !this.entities || !this.entities.physics || !this.layout) {
console.log('Missiing required data');
return;
}
const layout = this.layout
const entities = this.entities
const bubbles: BubbleEntity[] = items.map((item) => {
const randomX = randomPositionValue(layout.width);
const randomY = randomPositionValue(layout.height);
const body = Matter.Bodies.circle(
randomX,
randomY,
RADIUS, {
mass: 30,
}
);
body.label = item.id
return {
body,
item,
renderer: BubbleNode,
};
});
const physics = this.entities.physics as PhysicsEntity;
const allBodies = Matter.Composite.allBodies(physics.world)
bubbles.forEach((bubble) => {
//update existing or add new
const id = `bubble#${bubble.item.id}`;
if (Object.prototype.hasOwnProperty.call(entities, id)) {
//do physical node update here
//update scale and mass
const scale = bubble.item.scale
console.log('Updating item', id, scale);
//right her there used to be an issue because I used **bubble.body** which was not a correct reference to the world's body.
//so when I started to use allBodies array to find a proper reference of the body, everything started to work
let body = allBodies.find(item => item.label === bubble.item.id)
if (!!body) {
const scaledRadius = RADIUS*scale
const current = body.circleRadius || RADIUS
const scaleValue = scaledRadius/current
Matter.Body.scale(body, scaleValue, scaleValue)
}else{
console.warn('Physycal body not found, while the entity does exist');
}
} else {
console.log('Adding entity to the world');
entities[id] = bubble;
Matter.World.add(physics.world, [bubble.body]);
}
});
this.entities = entities
};
将来,我将改进该代码,我将为 body 使用一些变量,并将创建一个 matter.js 插件,使我能够扩展 [=54] =] 顺利而不是即时的,因为它现在可以工作。此外,上面的方法需要一些干净、简短的实现,而不是我试图让它工作的垃圾
您的示例并不完整;目前尚不清楚 MJS 引擎如何(或是否)运行ning。第一步是通过调用 Matter.Engine.update(engine);
in a requestAnimationFrame
循环来确保您有一个实际的渲染循环。
React 在这里似乎并不重要。它不应该影响结果,因为 MJS 引擎中每个 body 的 x/y 坐标和半径被提取并传递给视图组件,因此数据在一个方向上流动。我将在本示例的其余部分中省略它,但是一旦您对 MJS 端的工作感到满意,重新引入它应该很容易。
在 MJS 中缩放 body 的方法是调用 Matter.body.scale(body, scaleX, scaleY)
。此函数重新计算其他物理属性,例如 body.
有一个 annoying caveat 这个函数:它不是将绝对比例设置为 JS canvas 上下文或 CSS 转换可能,而是设置一个 相对规模。这意味着每次调用此函数都会更改未来调用的基线缩放比例。这样做的问题是舍入误差会累积并且会发生漂移。这也使得应用自定义补间变得困难。
解决方法可能取决于您希望实现的实际动画是什么(甚至可能不是必需的),因此除了建议编写与半径相关的逻辑外,我将避免规定任何过于具体的内容参考点以确保它保持在范围内。其他解决方法包括 re-creating 和每帧缩放圆圈。
使用圈子时的另一个陷阱是意识到 MJS circles are n-sided polygons, so small circles lose resolution when scaled up. Again, this is use-case dependent, but you may wish to create a Bodies.polygon
with more sides than would be created by Bodies.circle
如果您遇到异常行为。
即是说,这是一个最小的、完整的原始缩放示例,它向您展示了如何 运行 动画循环并调用 scale
以随时间动态调整它。将其视为概念证明,需要进行调整才能适用于您的用例(无论是什么)。
const engine = Matter.Engine.create();
const circles = [...Array(15)].map((_, i) => {
const elem = document.createElement("div");
elem.classList.add("circle");
document.body.append(elem);
const body = Matter.Bodies.circle(
// x, y, radius
10 * i + 60, 0, Math.random() * 5 + 20
);
return {
elem,
body,
offset: i,
scaleAmt: 0.05,
speed: 0.25,
};
});
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.body}
);
const walls = [
Matter.Bodies.rectangle(
// x, y, width, height
innerWidth / 2, 0, innerWidth, 40, {isStatic: true}
),
Matter.Bodies.rectangle(
innerWidth / 2, 180, innerWidth, 40, {isStatic: true}
),
Matter.Bodies.rectangle(
0, innerHeight / 2, 40, innerHeight, {isStatic: true}
),
Matter.Bodies.rectangle(
300, innerHeight / 2, 40, innerHeight, {isStatic: true}
),
];
Matter.Composite.add(
engine.world,
[mouseConstraint, ...walls, ...circles.map(e => e.body)]
);
/*
naive method to mitigate scaling drift over time.
a better approach would be to scale proportional to radius.
*/
const baseScale = 1.00063;
(function update() {
requestAnimationFrame(update);
circles.forEach(e => {
const {body, elem} = e;
const {x, y} = body.position;
const {circleRadius: radius} = body;
const scale = baseScale + e.scaleAmt * Math.sin(e.offset);
Matter.Body.scale(body, scale, scale);
e.offset += e.speed;
elem.style.top = `${y - radius / 2}px`;
elem.style.left = `${x - radius / 2}px`;
elem.style.width = `${2 * radius}px`;
elem.style.height = `${2 * radius}px`;
elem.style.transform = `rotate(${body.angle}rad)`;
});
Matter.Engine.update(engine);
})();
* {
margin: 0;
padding: 0;
}
body, html {
height: 100%;
}
.circle {
border-radius: 50%;
position: absolute;
cursor: move;
background: rgb(23, 0, 36, 1);
background: linear-gradient(
90deg,
rgba(23, 0, 36, 1) 0%,
rgba(0, 212, 255, 1) 100%
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>