在使用matter.js引擎库编写的plinko游戏中,如何让小球始终落入预先规划的位置

问题描述 投票:0回答:1

请以下面的代码为例。如何让球每次都看似随机落入底部的第三个空间位置?

我能想到的是在球下落的过程中不断判断它的碰撞位置,让球朝着目标移动,但如何让移动过程显得自然随机却难倒了我。 您可以在以下位置查看:https://codesandbox.io/s/charming-sunset-t7s794?file=/index.html

The effect I want to achieve

const Engine = Matter.Engine,
  Render = Matter.Render,
  World = Matter.World,
  Bodies = Matter.Bodies,
  Body = Matter.Body;

// create an engine
const engine = Engine.create();

// create a renderer
const render = Render.create({
  element: document.getElementById('can'),
  engine: engine
});
// create flanges
const fOptions = {
  isStatic: true,
}
const flanges = [
  //row one
  Bodies.polygon(45, 150, 3, 20, fOptions),
  Bodies.polygon(100, 250, 3, 20, fOptions),
  Bodies.polygon(60, 350, 3, 20, fOptions),
  Bodies.polygon(100, 450, 3, 20, fOptions),
  //row two
  Bodies.polygon(150, 150, 3, 20, fOptions),
  Bodies.polygon(200, 250, 3, 20, fOptions),
  Bodies.polygon(150, 350, 3, 20, fOptions),
  Bodies.polygon(200, 450, 3, 20, fOptions),
  //row three
  Bodies.polygon(250, 150, 3, 20, fOptions),
  Bodies.polygon(300, 250, 3, 20, fOptions),
  Bodies.polygon(250, 350, 3, 20, fOptions),
  Bodies.polygon(300, 450, 3, 20, fOptions),
  //row four
  Bodies.polygon(350, 150, 3, 20, fOptions),
  Bodies.polygon(400, 250, 3, 20, fOptions),
  Bodies.polygon(350, 350, 3, 20, fOptions),
  Bodies.polygon(400, 450, 3, 20, fOptions),
  //row five
  Bodies.polygon(450, 150, 3, 20, fOptions),
  Bodies.polygon(500, 250, 3, 20, fOptions),
  Bodies.polygon(450, 350, 3, 20, fOptions),
  Bodies.polygon(500, 450, 3, 20, fOptions),
  //row six
  Bodies.polygon(550, 150, 3, 20, fOptions),
  Bodies.polygon(600, 250, 3, 20, fOptions),
  Bodies.polygon(550, 350, 3, 20, fOptions),
  Bodies.polygon(600, 450, 3, 20, fOptions),
  //row seven
  Bodies.polygon(650, 150, 3, 20, fOptions),
  Bodies.polygon(700, 250, 3, 20, fOptions),
  Bodies.polygon(650, 350, 3, 20, fOptions),
  Bodies.polygon(700, 450, 3, 20, fOptions),
  //row 8
  Bodies.polygon(755, 150, 3, 20, fOptions),
  Bodies.polygon(740, 350, 3, 20, fOptions),
];
for (i = 0; i < flanges.length; i++) {
  World.add(engine.world, [flanges[i]])
  Body.setAngle(flanges[i], 16.23)
}
//create catches
const cOptions = {
  isStatic: true
}
const catches = [
  Bodies.rectangle(40, 560, 2, 40, cOptions)
];
xasx = 82;
for (v = 0; v < 20; v++) {
  catches.push(Bodies.rectangle(xasx, 560, 2, 40, cOptions))
  World.add(engine.world, [catches[v]])
  xasx = xasx + 45;
}
// create disk
let disk = [];
let count = 0;
const dOptions = {
  friction: .2,
  restitution: 1
}

function addD() {
  disk.push(Bodies.circle(event.offsetX, event.offsetY, 20, dOptions))
  World.add(engine.world, [disk[count]])
  count++
}
// create ground sky and walls
const ground = Bodies.rectangle(400, 610, 810, 60, {
  isStatic: true
});
const leftWall = Bodies.rectangle(0, 305, 2, 810, {
  isStatic: true
});
const righttWall = Bodies.rectangle(800, 305, 2, 810, {
  isStatic: true
});
const sky = Bodies.rectangle(400, 0, 810, 2, {
  isStatic: true
});
// add all of the bodies to the world
World.add(engine.world, [sky, righttWall, leftWall, ground]);

// run the engine
Engine.run(engine);

// run the renderer
Render.run(render);
* {
  margin: 0;
  touch-action: manipulation;
}

body {
  background-color: #353535;
}

h1 {
  font-family: 'Luckiest Guy', serif;
  color: white;
  background: -webkit-linear-gradient(pink, blueviolet);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  font-size: 100px;
  margin-bottom: 1%;
}

hr {
  width: 70%;
  margin-bottom: 2%;
  border-radius: 90%;
  background-color: pink;
  color: pink;
}

h4 {
  margin-top: 3%;
  color: darkorange;
}

a {
  color: darkorange;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>Plinko</title>
  <link href="styles.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Luckiest+Guy" />
</head>

<body>
  <center>
    <h1>PlinkO</h1>
    <hr />
    <aside onclick="addD()" id="can"></aside>
  </center>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/matter-js/0.19.0/matter.js"></script>
<script src="script.js"></script>

</html>

javascript collision matter.js
1个回答
0
投票

按照我的评论,一种方法是自己运行动画,捕获身体的位置,直到达到所需的行为。一旦获得位置数据数组,您就可以重新运行动画,通过逐步读取位置数据来设置实时身体位置。

这是一个概念证明,保证第一个“磁盘”将确定地落入第三个口袋(单击游戏区域创建磁盘):

const {Bodies, Body, Composite, Engine, Events, Render, Runner} = Matter;
const engine = Engine.create();
const render = Render.create({
  element: document.getElementById("can"),
  engine: engine,
});
const fOptions = {isStatic: true};
const flanges = [
  // row one
  Bodies.polygon(45, 150, 3, 20, fOptions),
  Bodies.polygon(100, 250, 3, 20, fOptions),
  Bodies.polygon(60, 350, 3, 20, fOptions),
  Bodies.polygon(100, 450, 3, 20, fOptions),
  // row two
  Bodies.polygon(150, 150, 3, 20, fOptions),
  Bodies.polygon(200, 250, 3, 20, fOptions),
  Bodies.polygon(150, 350, 3, 20, fOptions),
  Bodies.polygon(200, 450, 3, 20, fOptions),
  // row three
  Bodies.polygon(250, 150, 3, 20, fOptions),
  Bodies.polygon(300, 250, 3, 20, fOptions),
  Bodies.polygon(250, 350, 3, 20, fOptions),
  Bodies.polygon(300, 450, 3, 20, fOptions),
  // row four
  Bodies.polygon(350, 150, 3, 20, fOptions),
  Bodies.polygon(400, 250, 3, 20, fOptions),
  Bodies.polygon(350, 350, 3, 20, fOptions),
  Bodies.polygon(400, 450, 3, 20, fOptions),
  // row five
  Bodies.polygon(450, 150, 3, 20, fOptions),
  Bodies.polygon(500, 250, 3, 20, fOptions),
  Bodies.polygon(450, 350, 3, 20, fOptions),
  Bodies.polygon(500, 450, 3, 20, fOptions),
  // row six
  Bodies.polygon(550, 150, 3, 20, fOptions),
  Bodies.polygon(600, 250, 3, 20, fOptions),
  Bodies.polygon(550, 350, 3, 20, fOptions),
  Bodies.polygon(600, 450, 3, 20, fOptions),
  // row seven
  Bodies.polygon(650, 150, 3, 20, fOptions),
  Bodies.polygon(700, 250, 3, 20, fOptions),
  Bodies.polygon(650, 350, 3, 20, fOptions),
  Bodies.polygon(700, 450, 3, 20, fOptions),
  // row 8
  Bodies.polygon(755, 150, 3, 20, fOptions),
  Bodies.polygon(740, 350, 3, 20, fOptions),
];

for (let i = 0; i < flanges.length; i++) {
  Composite.add(engine.world, [flanges[i]]);
  Body.setAngle(flanges[i], 16.23);
}

const cOptions = {isStatic: true};
const catches = [Bodies.rectangle(40, 560, 2, 40, cOptions)];
let xasx = 82;

for (let v = 0; v < 20; v++) {
  catches.push(Bodies.rectangle(xasx, 560, 2, 40, cOptions));
  Composite.add(engine.world, [catches[v]]);
  xasx = xasx + 45;
}

const disk = [];
const dOptions = {friction: 0.2, restitution: 1};

function addD() {
  disk.push(
    Bodies.circle(event.offsetX, event.offsetY, 20, dOptions)
  );
  Composite.add(engine.world, [disk.at(-1)]);
}

const ground = Bodies.rectangle(400, 610, 810, 60, {isStatic: true});
const leftWall = Bodies.rectangle(0, 305, 2, 810, {isStatic: true});
const righttWall = Bodies.rectangle(800, 305, 2, 810, {isStatic: true});
const sky = Bodies.rectangle(400, 0, 810, 2, {isStatic: true});
Composite.add(engine.world, [
  sky,
  righttWall,
  leftWall,
  ground,
]);

const positions = getPositions();
let currPosition = 0;
Events.on(engine, "beforeUpdate", () => {
  if (disk[0] && currPosition < positions.length) {
    const pos = {
      x: positions[currPosition][0],
      y: positions[currPosition++][1],
    };
    Body.setPosition(disk[0], pos);
  }
});
Runner.run(engine);
Render.run(render);

function getPositions() {return [[66,63],[66,63.27],[66,63.83],[66,64.65],[66,65.75],[66,67.11],[66,68.73],[66,70.62],[66,72.77],[66,75.17],[66,77.82],[66,80.73],[66,83.89],[66,87.29],[66,90.93],[66,94.82],[66,98.95],[66,103.31],[66,107.91],[66,112.74],[66,117.79],[66,123.08],[66.88,128.08],[71.14,130.44],[75.36,133.06],[79.54,135.93],[83.67,139.05],[87.76,142.41],[91.82,146.02],[95.83,149.87],[99.80,153.96],[103.74,158.29],[107.63,162.85],[111.48,167.65],[115.30,172.67],[119.08,177.92],[122.82,183.40],[126.52,189.10],[130.18,195.02],[133.81,201.16],[137.41,207.51],[140.96,214.08],[144.48,220.86],[147.97,227.85],[151.42,235.05],[154.84,242.46],[158.22,250.07],[161.57,257.88],[163.76,266.09],[166.91,273.53],[170.02,281.17],[173.10,289.01],[176.15,297.05],[179.16,305.29],[182.15,313.72],[185.11,322.35],[188.04,331.17],[190.94,340.17],[193.81,349.37],[196.65,358.75],[199.47,368.32],[202.25,378.07],[205.01,387.99],[207.74,398.10],[210.44,408.38],[214.95,417.20],[224.36,420.72],[233.67,424.48],[242.89,428.48],[252.01,432.72],[261.05,437.20],[270.00,441.90],[270.22,441.88],[261.58,437.65],[253.02,433.74],[244.55,430.15],[236.16,426.87],[227.86,423.90],[219.64,421.24],[213.76,417.20],[214.19,409.81],[214.62,402.77],[215.04,396.07],[215.46,389.72],[215.87,383.72],[216.28,378.05],[216.69,372.71],[215.59,368.58],[211.37,367.48],[207.20,366.68],[203.07,366.16],[198.99,365.93],[194.94,365.97],[190.93,366.29],[186.97,366.89],[184.47,368.49],[186.39,371.71],[188.30,375.17],[190.19,378.88],[192.06,382.83],[193.92,387.02],[195.75,391.45],[197.57,396.11],[199.36,400.99],[201.14,406.11],[202.93,410.66],[204.46,405.76],[205.98,401.19],[207.48,396.95],[208.97,393.02],[210.44,389.41],[211.90,386.12],[213.34,383.13],[214.77,380.46],[216.19,378.08],[217.59,376.01],[218.97,374.25],[217.20,375.82],[215.45,377.65],[213.72,379.74],[212.01,382.09],[210.32,384.70],[208.64,387.55],[206.97,390.65],[205.33,394.00],[203.70,397.60],[202.09,401.44],[200.49,405.51],[198.91,409.82],[196.34,410.57],[193.56,407.66],[190.81,405.06],[188.09,402.76],[185.39,400.77],[182.73,399.07],[180.09,397.66],[177.47,396.55],[174.88,395.72],[172.32,395.19],[169.78,394.93],[167.27,394.96],[164.78,395.26],[162.32,395.84],[159.89,396.69],[157.47,397.81],[155.08,399.19],[152.72,400.84],[150.38,402.75],[148.06,404.92],[145.77,407.35],[143.50,410.03],[141.25,412.96],[139.02,416.14],[136.82,419.56],[134.63,423.23],[132.47,427.14],[130.34,431.29],[128.22,435.67],[128.14,439.12],[132.68,439.23],[137.18,439.62],[141.63,440.29],[146.03,441.22],[150.39,442.43],[154.71,443.90],[158.98,445.63],[163.21,447.63],[165.99,449.07],[162.16,446.61],[158.37,444.46],[154.62,442.60],[150.91,441.04],[147.23,439.78],[143.59,438.80],[139.99,438.11],[136.42,437.71],[132.88,437.59],[129.39,437.74],[127.30,437.38],[129.56,435.69],[131.79,434.29],[134.01,433.19],[136.20,432.37],[138.37,431.84],[140.52,431.60],[142.65,431.63],[144.75,431.94],[146.83,432.52],[148.90,433.38],[150.94,434.51],[152.96,435.90],[154.97,437.56],[156.95,439.48],[158.91,441.65],[160.85,444.08],[162.78,446.77],[164.68,449.71],[164.46,452.25],[161.41,453.35],[158.39,454.72],[155.40,456.36],[152.43,458.25],[149.50,460.41],[146.60,462.82],[143.72,465.48],[140.88,468.40],[138.06,471.56],[135.27,474.97],[132.51,478.63],[129.78,482.52],[127.07,486.66],[124.39,491.03],[121.74,495.63],[119.12,500.47],[116.52,505.54],[113.94,510.83],[111.40,516.35],[108.87,522.09],[106.38,528.05],[103.91,534.23],[102.94,540.62],[105.24,546.46],[106.05,552.51],[103.90,558.00],[102.88,560.18],[102.88,555.63],[102.77,552.19],[102.76,549.84],[102.96,548.62],[103.17,547.68],[103.37,547.04],[103.56,546.67],[103.76,546.59],[103.95,546.79],[104.15,547.26],[104.34,548.01],[104.53,549.02],[104.71,550.31],[104.90,551.86],[105.08,553.67],[105.26,555.74],[105.44,558.07],[105.62,560.21],[105.37,557.87],[105.12,555.84],[104.88,554.11],[104.63,552.67],[104.39,551.53],[104.16,550.67],[103.92,550.10],[103.69,549.82],[103.46,549.81],[103.23,550.08],[103.01,550.63],[102.87,551.45],[102.86,551.88],[102.87,552.28],[102.88,552.62],[102.88,552.93],[102.89,553.30],[102.90,553.72],[102.90,554.21],[102.90,554.78],[102.90,555.49],[102.89,556.32],[102.84,557.23],[102.81,558.19],[102.78,559.41],[102.83,560.17],[103.29,560.12],[103.69,560.17],[104.08,560.09],[104.45,560.12],[104.80,560.21],[105.14,560.05],[105.34,560.05],[105.53,560.06],[105.72,560.19],[105.99,560.08],[106.10,560.10],[106.01,560.22],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11],[106.11,560.11]];};
body {margin: 0;}
<script src="https://cdn.bootcdn.net/ajax/libs/matter-js/0.19.0/matter.js"></script>
<button>log positions</button>
<aside onclick="addD()" id="can"></aside>

大部分相关代码位于底部,靠近

beforeUpdate
事件处理程序。

这是我用来创建位置数组的“仪表化”代码:

const {Bodies, Body, Composite, Engine, Events, Render, Runner} = Matter;
const engine = Engine.create();
const render = Render.create({
  element: document.getElementById("can"),
  engine: engine,
});
const fOptions = {isStatic: true};
const flanges = [
  // row one
  Bodies.polygon(45, 150, 3, 20, fOptions),
  Bodies.polygon(100, 250, 3, 20, fOptions),
  Bodies.polygon(60, 350, 3, 20, fOptions),
  Bodies.polygon(100, 450, 3, 20, fOptions),
  // row two
  Bodies.polygon(150, 150, 3, 20, fOptions),
  Bodies.polygon(200, 250, 3, 20, fOptions),
  Bodies.polygon(150, 350, 3, 20, fOptions),
  Bodies.polygon(200, 450, 3, 20, fOptions),
  // row three
  Bodies.polygon(250, 150, 3, 20, fOptions),
  Bodies.polygon(300, 250, 3, 20, fOptions),
  Bodies.polygon(250, 350, 3, 20, fOptions),
  Bodies.polygon(300, 450, 3, 20, fOptions),
  // row four
  Bodies.polygon(350, 150, 3, 20, fOptions),
  Bodies.polygon(400, 250, 3, 20, fOptions),
  Bodies.polygon(350, 350, 3, 20, fOptions),
  Bodies.polygon(400, 450, 3, 20, fOptions),
  // row five
  Bodies.polygon(450, 150, 3, 20, fOptions),
  Bodies.polygon(500, 250, 3, 20, fOptions),
  Bodies.polygon(450, 350, 3, 20, fOptions),
  Bodies.polygon(500, 450, 3, 20, fOptions),
  // row six
  Bodies.polygon(550, 150, 3, 20, fOptions),
  Bodies.polygon(600, 250, 3, 20, fOptions),
  Bodies.polygon(550, 350, 3, 20, fOptions),
  Bodies.polygon(600, 450, 3, 20, fOptions),
  // row seven
  Bodies.polygon(650, 150, 3, 20, fOptions),
  Bodies.polygon(700, 250, 3, 20, fOptions),
  Bodies.polygon(650, 350, 3, 20, fOptions),
  Bodies.polygon(700, 450, 3, 20, fOptions),
  // row 8
  Bodies.polygon(755, 150, 3, 20, fOptions),
  Bodies.polygon(740, 350, 3, 20, fOptions),
];

for (let i = 0; i < flanges.length; i++) {
  Composite.add(engine.world, [flanges[i]]);
  Body.setAngle(flanges[i], 16.23);
}

const cOptions = {isStatic: true};
const catches = [Bodies.rectangle(40, 560, 2, 40, cOptions)];
let xasx = 82;

for (let v = 0; v < 20; v++) {
  catches.push(Bodies.rectangle(xasx, 560, 2, 40, cOptions));
  Composite.add(engine.world, [catches[v]]);
  xasx = xasx + 45;
}

const disk = [];
const dOptions = {friction: 0.2, restitution: 1};

function addD() {
  disk.push(
    Bodies.circle(event.offsetX, event.offsetY, 20, dOptions)
  );
  Composite.add(engine.world, [disk.at(-1)]);
}

const ground = Bodies.rectangle(400, 610, 810, 60, {isStatic: true});
const leftWall = Bodies.rectangle(0, 305, 2, 810, {isStatic: true});
const righttWall = Bodies.rectangle(800, 305, 2, 810, {isStatic: true});
const sky = Bodies.rectangle(400, 0, 810, 2, {isStatic: true});
Composite.add(engine.world, [
  sky,
  righttWall,
  leftWall,
  ground,
]);
const positions = [];
let currPosition = 0;
Events.on(engine, "beforeUpdate", () => {
  if (disk[0]) {
    positions.push(Object.values(disk[0].position));
  }
});
document.querySelector("button").addEventListener("click", () => {
  console.log(JSON.stringify(positions.map(e => e.map(e => +e.toFixed(2)))));
});
Runner.run(engine);
Render.run(render);
body {margin: 0;}
<script src="https://cdn.bootcdn.net/ajax/libs/matter-js/0.19.0/matter.js"></script>
<button>log positions</button>
<aside onclick="addD()" id="can"></aside>

我重复运行上面的检测片段,直到达到所需的运行,然后单击按钮记录位置。接下来,我将数据从浏览器控制台复制到剪贴板(这可以通过以编程方式完成)并复制到顶部片段中。

我为每个位置使用了一个数组来节省一点存储空间,这需要在调用

setPosition
之前将其转换为x/y向量对象。我还修剪了位置小数的一些精度以节省更多空间。这些可能是不成熟的优化,但对于许多用例来说最好记住。其他优化也是可能的。

上述概念验证方法的一种更优雅且可扩展的替代方法是在 Node 中运行 Matter.js 并将位置写入文件。如果所需的状态难以重现,您可以以编程方式重复运行 Matter.js 代码,并有条件地捕获位置数组以记录在文件中。这对于生成一系列可以随机选择的相似运行也很有用。对于此处的用例,一组全部到达第三个口袋的数十次运行可能足以显得真实。

更进一步,如果您使用 Matter.js 无头渲染完全确定性动画,则可以使用此处的技术来避免将 MJS 库或您的 MJS 代码完全发送到客户端 - 只需使用它来预先计算硬编码将数据结构放置在幕后并使用通用动画循环绘制它们!


类似主题:


一般来说,在变量声明之前始终使用

let
const
。很容易忘记在
for
循环中执行此操作,但如果不这样做,则会引入一个全局变量,该全局变量很容易与窗口变量发生冲突,或者为错误设置别名。

我还更改了代码以删除不必要的注释并避免使用已弃用的 MJS 类,例如 World。

© www.soinside.com 2019 - 2024. All rights reserved.