稀土掘金技术社区 01月22日
我用js做了个超级玛丽
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文讲述如何用js写超级玛丽游戏的MVP版本,主要用到leaferjs + vue框架,涵盖背景、场景、精灵、物理引擎、相机等方面,还包括分数系统、胜利机制和地图编辑等内容。

🎮用leaferjs的Group + Rect实现游戏背景,达到无限循环效果

🏞创建场景,用Canvas实现,存放精灵数组并绘制

🧚‍♂️创建精灵基类Sprite,子类SpriteMario实现自身绘制和位置更新

🔧实现物理引擎,包括重力、碰撞检测、跳跃等

📷引入相机概念,只绘制视口内内容并跟随人物

💯引入分数系统,根据游戏行为出现数字

🚩增加胜利机制,人物与旗帜碰撞判定胜利

🗺用vue做地图编辑器,可自定义关卡

原创 hhzzcc 2025-01-20 08:30 重庆

点击关注公众号,“技术干货”及时达!

点击关注公众号,“技术干货” 及时达!

前言

从刚接触编程起,作者就一直有一个做游戏的梦,奈何水平比较差,做不出啥太像样的游戏,最近趁着下班和周末时间折腾折腾,怀旧一下童年最爱玩的超级玛丽。本文将讲述如何用js写一个MVP版本的超级玛丽游戏,主要用到框架有leaferjs + vueleaferjs负责做图形渲染,vue负责界面和地图编辑,leaferapi用起来还是很舒服的,学习成本低,作者也非常热情,这里帮忙推荐一下~

本文会尽可能简单的少贴代码,并把核心的流程讲清楚,先上一张运行后的效果图,源码和体验地址在最下面

开始我们的游戏梦

1、创建背景

就是游戏背后的蓝天白云背景,背景素材如下

在游戏中,背景会随人物前进而后退,但是由于素材宽度有限,不可能无限后退,所以做了个逻辑,当背景的x到达边界的时候,把x设置成0,达到类似无限循环背景的效果

这里使用leaferGroup + Rect实现,由一个Background类来实现,用leafer实现简单很多

2、创建场景

场景用来承接和绘制游戏中的精灵,所以需要有个存放精灵的数组,以及一个run方法来绘制精灵(这里使用leaferCanvas实现),具体要绘制什么,解耦给各个精灵内部自由实现

class Scene {
sprites = [];

// 添加内容
add(sprite) {
this.sprites.push(sprite);
}

run() {
// 绘制内容,传入canvas上下文
this.sprites.forEach((sprite) => {
sprite.draw(context)
})
...
}
}

3、创建精灵

精灵是指场景中的元素,比如玛丽、砖块、成长蘑菇、敌人蘑菇等,我们可以为这些精灵创建一个基类Sprite,有xy表示位置,有widthheight表示宽高,vxvy表示水平和垂直方向速度

class Sprite {
constructor(options) {
const { x, y, width, height, vx, vy } = options;
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.width = width;
this.height = height;
}
}

场景中所有精灵都可以继承于他,同时上面也说到了绘制精灵精灵自身来决定,所以有个draw方法,且由于受到速度的影响,精灵的xy需要做更新

class SpriteMario extends Sprite {
constructor(options) {
super(options);
// ...
}

draw(context) {
this.x += this.vx;
this.y += this.vy;
context.drawImage(this.resource, this.x, this.y, this.width, this.height)
}
}

精灵需要根据自身的状态在当前类内维护自己的动画帧,比如当玛丽的vx > 0,要绘制的图片就是玛丽向右走的动画帧,实现后,效果如下(这里我降低了gif的帧数,实际运行是很流畅的,下面的gif也是)

然后我们依次绘制出其他砖块、石块、道具等精灵,这里先提前说一下,背景的运动、场景的绘制、包括底下的相机跟随、物理引擎都在每一帧即requestAnimationFrame中执行,代码如下

run() {
// 运行物理引擎
this._physicsEngine.run({
camera: this.camera,
scene: this.scene,
});
// 运行场景中的精灵
this.scene.run();
// 运行背景
this._background.run();
// 相机跟随玛丽
this.camera.x =
this._mario.x < MARIO_VIEW_OFFSET
? 0
: this._mario.x - MARIO_VIEW_OFFSET;
requestAnimationFrame(this.run.bind(this));
}

实现后,效果如下

看起来很有感觉有没有!接下来开始做物理引擎

4、物理引擎 - 重力

最先要实现的当然就是重力拉,运用初中物理知识,使用公式套进去

class PhysicsEngine {
run (options) {
// ...

// 为了防止过快给了个10的速度阈值
sprite.vy = Math.min(10, sprite.vy + G);
}
}

效果如下

5、物理引擎 - 碰撞检测

加上重力后,玛丽会往下掉,然后来实现碰撞检测,这里的思路比较简单,用到的是矩形之间的碰撞检测,先校验两个矩形是否发生碰撞,如果发生碰撞在校验碰撞来源的方向,并根据方向做位置修补,防止碰撞后嵌入,感兴趣可以看看源码,加上之后的效果如下

6、物理引擎 - 跳跃

跳跃套用上抛运动公式实现,给他一个默认的初速度v0

  sprite.v0 -= G;
sprite.vy = -sprite.v0;

然后当长按 "上键" 的时候,增大v0,达到按得越久跳的越高的效果,效果如下

7、物理引擎 - 其他精灵间的碰撞

这里就挑几个主要的来讲,其他实现都大同小异

7.1、人物顶到问号

问号中的道具会缓缓升起,前者的实现逻辑是给个参数active来指定当前道具精灵状态,当activefalse不会受到物理引擎影响,所以我们可以在问号被碰撞后,让道具y值不断变小,当完全露出时设置其activetrue,并给道具一个vx,效果如下

顺便说一下,当玛丽的头碰撞到建筑的底部时,会取消其上抛运动状态,使其只受重力影响

7.2、人物吃成长蘑菇

这个就比较简单了,当人物和蘑菇发生碰撞,将蘑菇销毁,人物的height变高,然后更新人物动画帧,效果如下

7.3、人物顶碎砖块

当人物的顶部碰撞到砖块的底部时,砖块销毁,然后将砖块拆成4个,向四周做抛物线运动,抛物线轨迹实现主要用一元二次方程实现,这里贴下核心代码

  this.animatedX += 2;
this.animatedY = 0.1 * this.animatedX * this.animatedX - b * this.animatedX;

运行后,效果如下

8、引入相机

这里的相机不是 webgl 的相机,可以理解成视图窗口,引入这个概念的目的是为了让绘制和物理引擎的执行只有在视口内才会执行,即只会绘制相机视口的内容,然后我们让相机的 x 跟随玛丽,这样就能达到跟随人物前进的效果

  camera.x = mario.x

效果如下

9、 引入分数系统

击杀怪物、吃道具、顶碎石砖,会出现数字,数字会跟随对应精灵,这里用的是leaferText很轻松就能实现,效果如下

10、增加胜利机制

创建旗帜,当人物和旗帜发生碰撞后,判定胜利,效果如下

11、编辑地图

游戏做完后,要开始做地图了,由于地图要一个个手动去敲xy很麻烦,就用vue做了个地图编辑器,可以通过鼠标来创建精灵自定义关卡,效果如下

结语

上面只是一个超级玛丽的MVP版本,还有挺多没实现的,比如音乐、乌龟等等,胜利的动画也比较简陋,后续如果有空再慢慢完善他,源码已经发布github,这里是源码地址[1]和体验地址[2],喜欢的话求个star~

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

超级玛丽 JS leaferjs vue 物理引擎
相关文章