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>