欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > 【前端】Matter实战:HTML游戏”命悬一线“

【前端】Matter实战:HTML游戏”命悬一线“

2024/10/23 19:35:44 来源:https://blog.csdn.net/2303_80346267/article/details/143062966  浏览:    关键词:【前端】Matter实战:HTML游戏”命悬一线“

在本教程中,我们将使用Matter.js构建一个简单的物理游戏,称为**“命悬一线”**(建议手机游玩),在文章末尾附完整代码。游戏的目标是控制一个小球,避免让连接在一起的线段掉到地面上。当线段的一部分接触到地面时,游戏结束。通过本教程,你将逐步了解如何在网页中使用Matter.js创建2D物理世界,并实现基本的用户交互。

准备工作

首先需要一个HTML文件来托管我们的游戏,并引入Matter.js库。Matter.js是一个JavaScript物理引擎,可以轻松创建真实的物理效果。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>命悬一线</title><style>body {margin: 0;overflow: hidden;  <!-- 避免滚动条出现 -->}canvas {background: #f0f0f0;display: block;width: 100%;  <!-- 适配屏幕宽度 -->height: 100vh; <!-- 适配屏幕高度 -->}</style>
</head>
<body><!-- 引入Matter.js --><script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script><script>

初始化物理引擎

接下来,我们将创建一个物理引擎并渲染到页面中。这部分代码负责设置Matter.js引擎和渲染器。

// 引入 Matter.js 的关键模块
const { Engine, Render, Runner, World, Bodies, Events } = Matter;// 创建引擎
const engine = Engine.create();  // Engine.create()初始化物理引擎
const world = engine.world;      // 获取引擎的世界// 创建渲染器,将物理世界显示在网页上
const render = Render.create({element: document.body,  // 将渲染器附加到HTML文档的body元素engine: engine,          // 绑定创建的物理引擎options: {width: window.innerWidth,  // 设置渲染器宽度,动态适配浏览器窗口height: window.innerHeight, // 设置渲染器高度,动态适配浏览器窗口wireframes: false           // 设置为false以禁用线框模式,使用实际的渲染}
});Render.run(render);  // 启动渲染器
const runner = Runner.create();  // 创建物理引擎的运行循环
Runner.run(runner, engine);      // 启动物理引擎的运行

添加游戏边界

为了让物体不飞出屏幕,我们需要在屏幕四周创建边界:地面、左右墙壁和天花板。这些物体是静止的(isStatic: true),即它们不会移动。

// 创建地面、墙壁和天花板,确保它们是静止的
const ground = Bodies.rectangle(window.innerWidth / 2, window.innerHeight, window.innerWidth, 60, { isStatic: true });
const leftWall = Bodies.rectangle(0, window.innerHeight / 2, 60, window.innerHeight + 100, { isStatic: true });
const rightWall = Bodies.rectangle(window.innerWidth, window.innerHeight / 2, 60, window.innerHeight + 100, { isStatic: true });
const ceiling = Bodies.rectangle(window.innerWidth / 2, -40, window.innerWidth, 60, { isStatic: true });// 将边界添加到世界中
World.add(world, [ground, leftWall, rightWall, ceiling]);

创建玩家可控制的球

我们创建一个静止的小球,玩家可以通过鼠标或触摸来控制它。球的质量很大,目的是使它具有足够的重量来压住物理链条。

// 创建玩家控制的小球
const followBall = Bodies.circle(window.innerWidth / 2, window.innerHeight - 300, 30, {friction: 0,  // 设置摩擦力为0,使小球容易滑动mass: 10000,  // 大质量使小球具有更大的影响力render: {fillStyle: 'rgba(0, 255, 255, 0.5)',  // 设置小球为半透明蓝色strokeStyle: 'white',  // 边框颜色lineWidth: 5           // 边框宽度},isStatic: true  // 初始化为静止
});
World.add(world, followBall);

创建物理链条

这部分是游戏的核心,我们将创建一系列的小段,它们连接在一起形成链条。每个段之间使用Matter.js的Constraint(约束)来连接。

const segments = [];  // 存储所有线段
const segmentCount = 50;  // 设置链条的段数
const segmentRadius = 2;  // 每段的半径
const segmentSpacing = 4;  // 段间距// 生成链条段
for (let i = 0; i < segmentCount; i++) {const xPosition = (window.innerWidth / 2) - (segmentCount / 2) * segmentSpacing + i * segmentSpacing;const segment = Bodies.circle(xPosition, 100, segmentRadius, {friction: 0,restitution: 0.6,  // 弹性设置render: {fillStyle: 'green'  // 线段的颜色}});segments.push(segment);World.add(world, segment);// 使用约束连接相邻的线段,形成刚性链条if (i > 0) {const constraint = Matter.Constraint.create({bodyA: segments[i - 1],  // 连接前一个线段bodyB: segment,          // 连接当前线段length: segmentRadius * 2,  // 约束长度stiffness: 1  // 刚度设置为1,表示刚性连接});World.add(world, constraint);}
}

添加重力

为了使线段能够自然下垂并随时间移动,我们需要调整引擎的重力。我们将重力设置为较小的值,这样物体会缓慢下落。

// 调整引擎的重力,使链条缓慢下落,该参数适合手机版,电脑自行调节
engine.gravity.y = 0.1;

控制小球的移动

通过鼠标或触摸事件来控制小球的移动,当按下鼠标或触摸屏幕时,小球会跟随指针的位置。

let isMousePressed = false;  // 用于检测鼠标是否按下// 当按下鼠标时,小球跟随指针
window.addEventListener('mousedown', () => {isMousePressed = true;
});
window.addEventListener('mouseup', () => {isMousePressed = false;
});// 鼠标移动时更新小球位置
window.addEventListener('mousemove', (event) => {if (isMousePressed) {const mouseX = event.clientX;const mouseY = event.clientY;Matter.Body.setPosition(followBall, { x: mouseX, y: mouseY });}
});// 支持触摸设备
window.addEventListener('touchstart', (event) => {isMousePressed = true;
});
window.addEventListener('touchend', () => {isMousePressed = false;
});
window.addEventListener('touchmove', (event) => {if (isMousePressed) {const touchX = event.touches[0].clientX;const touchY = event.touches[0].clientY;Matter.Body.setPosition(followBall, { x: touchX, y: touchY });}
});

检测游戏失败

如果链条的任何部分触碰到地面,游戏将结束。

let gameOver = false;// 监听碰撞事件,判断是否有线段触碰到地面
Events.on(engine, 'collisionStart', (event) => {event.pairs.forEach(pair => {if ((segments.includes(pair.bodyA) && pair.bodyB === ground) || (segments.includes(pair.bodyB) && pair.bodyA === ground)) {if (!gameOver) {gameOver = true;alert('游戏结束! 最终得分: ' + score);location.reload();  // 重新加载页面以重置游戏}}});
});

计分系统

我们添加一个简单的计分系统,随着时间推移得分会增加,直到游戏结束。

let score = 0;  // 初始化分数
const scoreDiv = document.createElement('div');
scoreDiv.style.position = 'absolute';
scoreDiv.style.top = '10px';
scoreDiv.style.left = '10px';
scoreDiv.style.fontSize = '20px';
scoreDiv.style.color = 'white';  // 分数显示为白色
scoreDiv.innerText = '分数: 0';
document.body.appendChild(scoreDiv);// 每秒更新一次分数
setInterval(() => {if (!gameOver) {score++;scoreDiv.innerText = '分数: ' + score;}
}, 1000);

添加水印

最后,我们可以在游戏中添加水印,以标识制作者信息。

const watermark = document.createElement('div');
watermark.style.position = 'absolute';
watermark.style.bottom = '10px';
watermark.style.right = '15px';
watermark.style.fontSize = '14px';
watermark.style.color = 'rgba(255, 255, 255, 0.7)';
watermark.innerText = 'Made By Touken';  // 制作者信息
document.body.appendChild(watermark);

完整代码

最后,你可以将所有代码合并到一个HTML文件中,运行它便可以体验这个小游戏了。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>命悬一线</title><style>body {margin: 0;overflow: hidden;/* 防止出现滚动条 */}canvas {background: #f0f0f0;display: block;margin: 0 auto;width: 100%;/* 适配手机 */height: 100vh;/* 全屏高度 */}</style>
</head><body><script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script><script>// 引入 Matter.js  const { Engine, Render, Runner, World, Bodies, Events, MouseConstraint, Mouse } = Matter;// 创建引擎  const engine = Engine.create();const world = engine.world;// 创建渲染器  const render = Render.create({element: document.body,engine: engine,options: {width: window.innerWidth,height: window.innerHeight,wireframes: false}});Render.run(render);const runner = Runner.create();Runner.run(runner, engine);// 创建地面和左右墙壁  const ground = Bodies.rectangle(window.innerWidth / 2, window.innerHeight, window.innerWidth, 60, { isStatic: true });const leftWall = Bodies.rectangle(0, window.innerHeight / 2, 60, window.innerHeight + 100, { isStatic: true });const rightWall = Bodies.rectangle(window.innerWidth, window.innerHeight / 2, 60, window.innerHeight + 100, { isStatic: true });World.add(world, [ground, leftWall, rightWall]);// 创建顶部天花板  const ceiling = Bodies.rectangle(window.innerWidth / 2, -40, window.innerWidth, 60, { isStatic: true });World.add(world, ceiling);// 创建跟随的球,初始位置在线的下方  const followBall = Bodies.circle(window.innerWidth / 2, window.innerHeight - 300, 30, {friction: 0,mass: 10000,render: {fillStyle: 'rgba(0, 255, 255, 0.5)', // 使用半透明的蓝色  strokeStyle: 'white', // 添加边框颜色  lineWidth: 5, // 设置边框宽度  // 自定义图形,使用 `beforeRender` 来绘制渐变或阴影  sprite: {texture: '',  // 可以添加对应的纹理或图像路径  xScale: 1, // 调节纹理的缩放  yScale: 1}},isStatic: true});World.add(world, followBall);// 创建线,水平排列  const segments = [];const segmentCount = 50;  // 减少段数以适配小屏幕  const segmentRadius = 2;const segmentSpacing = 4;for (let i = 0; i < segmentCount; i++) {const xPosition = (window.innerWidth / 2) - (segmentCount / 2) * segmentSpacing + i * segmentSpacing;const segment = Bodies.circle(xPosition, 100, segmentRadius, {friction: 0,frictionAir: 0,restitution: 0.6,render: {fillStyle: 'green'}});segments.push(segment);World.add(world, segment);// 创建约束连接,去除弹性  if (i > 0) {const constraint = Matter.Constraint.create({friction: 0,bodyA: segments[i - 1],bodyB: segment,length: segmentRadius * 2,stiffness: 1});World.add(world, constraint);}}// 设置较小的重力,使物体下落缓慢  engine.gravity.y = 0.1;// 触摸控制:按下时小球跟随,松开时停止跟随  let isMousePressed = false;const handleMouseStart = (event) => {isMousePressed = true;};const handleMouseEnd = () => {isMousePressed = false;};const handleMouseMove = (event) => {if (isMousePressed) {const mouseX = event.clientX;const mouseY = event.clientY;// 将鼠标位置映射到 canvas 中  const canvasBounds = render.canvas.getBoundingClientRect();const mousePosition = {x: mouseX - canvasBounds.left,y: mouseY - canvasBounds.top};// 更新小球位置  Matter.Body.setPosition(followBall, mousePosition);}};window.addEventListener('mousedown', handleMouseStart);window.addEventListener('mouseup', handleMouseEnd);window.addEventListener('mousemove', handleMouseMove);// 支持移动设备触摸  window.addEventListener('touchstart', (event) => {event.preventDefault();  // 防止默认的触摸事件  isMousePressed = true;});window.addEventListener('touchend', handleMouseEnd);window.addEventListener('touchmove', (event) => {event.preventDefault();  // 防止滚动  if (isMousePressed) {const touchX = event.touches[0].clientX;const touchY = event.touches[0].clientY;const canvasBounds = render.canvas.getBoundingClientRect();const touchPosition = {x: touchX - canvasBounds.left,y: touchY - canvasBounds.top};// 更新小球位置  Matter.Body.setPosition(followBall, touchPosition);}});// 游戏失败检测  let gameOver = false;Events.on(engine, 'collisionStart', function (event) {event.pairs.forEach(function (pair) {if (segments.includes(pair.bodyA) && pair.bodyB === ground ||segments.includes(pair.bodyB) && pair.bodyA === ground) {if (!gameOver) {gameOver = true;alert('游戏结束! 最终得分: ' + score);location.reload();}}});});// 游戏计分  let score = 0;const scoreDiv = document.createElement('div');scoreDiv.style.position = 'absolute';scoreDiv.style.top = '10px';scoreDiv.style.left = '10px';scoreDiv.style.fontSize = '20px';scoreDiv.style.color = 'white'; // 设置分数颜色为白色    scoreDiv.innerText = '分数: 0';document.body.appendChild(scoreDiv);// 计时更新  setInterval(() => {if (!gameOver) {score++;scoreDiv.innerText = '分数: ' + score;}}, 1000);// 启动引擎,运行 Matter.js  World.add(world, [ground, followBall]);// 创建水印  const watermark = document.createElement('div');watermark.style.position = 'absolute';watermark.style.bottom = '10px'; // 距离底部 10 像素  watermark.style.right = '15px';  // 距离右侧 15 像素  watermark.style.fontSize = '14px'; // 字体大小  watermark.style.color = 'rgba(255, 255, 255, 0.7)'; // 设置为白色并带有透明度  watermark.innerText = 'Made By Touken'; // 水印文本  document.body.appendChild(watermark);  </script>
</body></html>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com