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

news2024/10/22 18:27:35

在本教程中,我们将使用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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

International Symposium on Artificial Intelligence Innovations

计算机科学&#xff08;Computer Science&#xff09;&#xff1a; 算法、自动化软件工程、生物信息学和科学计算、计算机辅助设计、计算机动画、计算机体系结构、计算机建模、计算机网络、计算机安全、计算机图形学与图像处理、数据库与数据挖掘、数据压缩、数据加密、数字信号…

【渗透测试】-红日靶场-获取web服务器权限

拓扑图&#xff1a; 前置环境配置&#xff1a; Win 7 默认密码&#xff1a;hongrisec201 内网ip:192.168.52.143 打开虚拟网络编辑器 添加网络->VMent1->仅主机模式->子网ip:192.168.145.0 添加网卡&#xff1a; 虚拟机->设置-> 添加->网络适配器 保存&a…

C++ —— set系列的使用

目录 1. 序列式容器和关联式容器 2. set和multiset参考⽂档 3. set类的介绍 4. set的构造和迭代器 4.1 set的增删查 4.1.1 插入 4.1.2 查找 4.1.3 删除 5. multiset和set的差异 1. 序列式容器和关联式容器 前⾯我们已经接触过STL中的部分容器如&#xff1a;str…

【数据库设计】逻辑结构设计

E-R实体集的转换 概念结构设计之后就是对E-R图进行逻辑结构设计&#xff1a;即将E-R图转化成关系的过程。逻辑结构设计主要用于数据库管理系统上&#xff0c;为了让数据在计算机系统中更好地表示。 此设计过程用到的数据模型有&#xff1a;除了前面讲过的关系模型还有层次模型…

如何看一个flutter项目的具体flutter版本

查看pubspec.lock文件 这个项目实际运行的就是 flutter 3.16.6 版本的

详解Java之Spring MVC篇一

目录 Spring MVC 官方介绍 MVC RequestMapping 传递参数 无参数 单个参数 针对String类型 针对Integer类型 针对int类型 针对自定义类型 多个参数 参数重命名 参数强制一致 参数不强制一致 传递数组 ​编辑传递List ​编辑 传递JSON ​编辑 从路径中获取参…

你们想要的格姗导航网站终于开源了!

关注小格子的读者们&#xff0c;应该都知道&#xff0c;小格子自己做了一个导航网站。 通过这几天的代码和文档整理&#xff0c;决定把导航网站给大家开源。目前只开源基础版&#xff0c;大家可以根据此开源项目开发自定义导航。 格姗导航网站 一个基于 Spring Boot、MyBati…

文献阅读:通过深度神经网络联合建模多个切片构建3D整体生物体空间图谱

文献介绍 文献题目&#xff1a; 通过深度神经网络联合建模多个切片构建3D整体生物体空间图谱 研究团队&#xff1a; 杨灿&#xff08;香港科技大学&#xff09;、吴若昊&#xff08;香港科技大学&#xff09; 发表时间&#xff1a; 2023-10-19 发表期刊&#xff1a; Nature M…

Vs配置opencv库 实例,opencv选用4.9.0版本,vs版本是2022社版,学习笔记不断更新

课程链接 贾志刚老师opencv入门课程 备注&#xff1a;由于课程好几年前了&#xff0c;直接将环境配置为opencv4.9.0vs22 参考&#xff1a; 参考搭建环境 opencv下载环境&#xff1a;opencv vs22opencv4.9.0 创建一个文件夹 并修改下下面的目录&#xff0c;我的目录是F:\opencv…

LinkedList和链表之刷题课(上)

在上一节,我们自己实现了一个单链表的结构,接下来我们来写几个面试题巩固一下 1. 删除链表中等于给定val的所有结点 解析题目: 如图,我们需要删除所有的值为12的结点 在head非空的情况下,我们删除一个结点,首先要找到这个结点,然后把这个结点的前面一个结点的next指向这个节点…

neutron组件

1.实现虚拟交换机有两种方式 2.HCS网络节点 华为 HCS 将网络节点单独部署&#xff0c;且部署两台(主备部署) 两张万兆网卡&#xff0c;否则检测无法通过 L3 agent 部署在哪个节点&#xff0c;哪个节点就是网络节点 DHCP agent metadata agent 3.neutron概念 3.1Neutron支持…

10. DAX 时间函数之实战

在实际代码过程中&#xff0c;总会遇到各种需求&#xff0c;往往需要一个或者多个函数一起实现目的。在下面的过程中&#xff0c;我尽量使用简洁而优美的代码&#xff0c;来实现各个目的。 首先准备数据&#xff0c;使用 1. DAX 时间函数--生成日期表 中的 GENERATE CALENDAR …

服务器卸载 mysql

服务器卸载 mysql 1.卸载安装的mysql #查看安装的mysql rpm -qa|grep -i mysql在这里插入图片描述 #卸载 rpm -ev 名称 --nodeps rpm -ev mysql-libs-8.0.26-1.module_el8.4.0915de215114.x86_64 --nodeps2.删除相关文件 find / -name mysql rm -rf /var/lib/mysql3.删除配…

西南交通大学计算机软件专业上岸难度分析

C哥专业提供——计软考研院校选择分析专业课备考指南规划 西南交通大学计算机科学与技术2024届考研难度整体呈现"稳中有升"的态势。学硕实际录取33人&#xff0c;复试分数线362分&#xff0c;复试录取率71.74%&#xff1b;专硕&#xff08;计算机技术&#xff09;实际…

Java 输入与输出(I/O)流的装饰流【处理流】

Java I/O流的装饰流 按照Java 输入与输出&#xff08;I/O)流的处理功能&#xff1a;I/O流可分为低级的节点流和高级的装饰流&#xff08;又称处理流&#xff09;。 节点流是直接从数据源&#xff08;数据源可以是文件、数组、内存或网络&#xff09;读/写数据的输入输出流&am…

021_Thermal_Transient_in_Matlab统一偏微分框架之热传导问题

Matlab求解有限元专题系列 固体热传导方程 固体热传导的方程为&#xff1a; ρ C p ( ∂ T ∂ t u t r a n s ⋅ ∇ T ) ∇ ⋅ ( q q r ) − α T d S d t Q \rho C_p \left( \frac{\partial T}{\partial t} \mathbf{u}_{\mathtt{trans}} \cdot \nabla T \right) \nab…

三个必须了解的知乎知+广告账户知识!

作为国内领先的问答社区&#xff0c;知乎以其高质量的内容和深度讨论吸引了大量专业和兴趣导向的用户群体。对于希望精准触达目标用户的广告主来说&#xff0c;知乎的信息流广告无疑是一个不可多得的营销渠道&#xff0c;云衔科技助力企业知乎广告开户投放及代运营服务。 1. 知…

【rom分享】PSP体育游戏大众高尔夫玩法介绍,不要错过

各位新老观众大家好&#xff0c;今天我将介绍知名掌机PSP的所有游戏&#xff0c;PSP的游戏库非常庞大&#xff0c;随着PSP模拟器的普及&#xff0c;你可以在安卓和苹果两大平台的移动设备上游玩&#xff0c;也可以在PC上面游玩&#xff0c;当然你也可以收藏一个PSP掌机进行游玩…

python3的语法及入门(近7000字,耐心看包全,看完记得点赞)!

1. Python3 基础语法 缩进&#xff1a;Python 使用缩进来表示代码块&#xff0c;通常使用 4 个空格。 语句&#xff1a;一行代码就是一个语句。 变量&#xff1a;不需要声明类型&#xff0c;直接赋值即可。 2. Python3 基本数据类型 Python 中的基本数据类型包括整数、浮点…

Shell学习——shell中的变量

目录 一、父shell和子shell&#xff1a; 二、系统预定变量 定义方式&#xff1a; 脚本举例 ​编辑 四、只读变量 五、撤销变量 六、小结 七、特殊变量 $n $# $*、$ $? 一、父shell和子shell&#xff1a; 由于shell的原理可以理解为套娃&#xff0c;因此有父shell…