第一个微信小游戏。哈哈又是第一个。因为此次疫情,宅在家里,不再找点事来做就真的快发霉了,所以就有了这个小游戏。
开源
github地址:github.com/CB-ysx/bear…
说明:只开源第一版代码。 何为第一版,就是用webpack+babel打包的版本。 这个是参考JetLua提供的pixi开发框架以及colloc微信小游戏的版本。 colloc源码地址:github.com/JetLua/coll… 感谢作者分享的pixi.js开发微信小游戏。
因为都是使用webpack+babel打包,导致打包后文件里npm第三方包代码一样,重复率太高,微信小游戏识别为抄袭。。。解释了也没用,一直不给审核通过,还扣了信用分。。。 所以这一版仅供参考。
后来我就重构了一版,去掉了webpack打包。
微信小游戏码
游戏部分截图
游戏玩法
点击屏幕左右两边,控制小熊躲避障碍物向上左右两边跳,跳越多层得分越高,踩到星星道具可直接加10分。
源码介绍
github地址:github.com/CB-ysx/bear…
框架
使用webpack+babel打包,采用JetLua提供的pixi开发框架。
目录结构
说明:
- readmeimg目录不用管,这是我在github显示部分游戏截图存放的图片。
- 源码主要是src目录下
game.json和project.config.json是微信小游戏必须的文件,可直接通过开发者工具创建项目后复制过来,当然为了配合本项目结构,需要修改game.json的内容。当然也可以直接使用本项目的game.json文件即可。
static目录存放一些静态资源,当然如果资源太大,可以选择存放在cdn之类的。 cloud目录存放的是云函数,本项目没有额外开发后端项目,直接使用小游戏提供的云开发。 game目录是游戏主域的源码 openDataContext目录是游戏子域的源码
主域:可理解为玩家可见的绘制域 子域:绘制的canvas对玩家不可见,需要在主域获取子域的canvas绘制出去,子域主要用来绘制一些敏感数据,比如用户的好友数据、群组数据等,该类数据微信不直接提供给开发者使用,只能从子域获取。
使用的主要的npm包
本游戏使用pixi.js开发,由于微信小游戏是没有dom,只有canvas,直接使用pixi.js会有一些问题,需要加多个adapter来做兼容,这里就直接使用JetLua提供的。
- @iro/wechat-adapter // 适配器
- popmotion // 做动画
- pixi
- @pixi/unsafe-eval
如何使用pixi.js开发
主要看src/game/core/core.js文件
const ticker = PIXI.Ticker.shared;
const loader = PIXI.Loader.shared; // 用于加载图片
const stage = new PIXI.Container(); // 舞台,之后所有的场景都是放在该舞台上显示出来
const monitor = new PIXI.utils.EventEmitter();
const pixelRatio = Math.min(2, devicePixelRatio);
const renderer = new PIXI.Renderer({ // 使用webgl
view: canvas, // canvas在window上,直接使用
antialias: true,
backgroundColor: 0x171a24,
width: width * pixelRatio,
height: height * pixelRatio
});
const zoom = {
mix: [
renderer.screen.width / design.width,
renderer.screen.height / design.height
],
get max() {
return Math.max(...this.mix);
},
get min() {
return Math.min(...this.mix);
}
};
renderer.plugins.accessibility.destroy();
renderer.plugins.interaction.mapPositionToPoint = (point, x, y) => { // 适配点击事件
point.set(x * pixelRatio, y * pixelRatio);
};
ticker.add(() => renderer.render(stage)); // 渲染
入口
app.js 引入core.js等文件,然后做一些监听(页面显示,路由跳转等)
这里提下音乐播放相关问题,小游戏挂起(回到微信页面等)会触发暂停播放音乐,然而再次打开小游戏时,音乐并不会继续播放。查了下可使用微信的接口来监听阻断音乐播放事件。
wx.onAudioInterruptionBegin(()=> {
// 阻断音乐播放
console.log('onAudioInterruptionBegin');
});
wx.onAudioInterruptionEnd(()=> {
console.log('onAudioInterruptionEnd');
// 此时回到游戏页面了,发布该事件
monitor.emit('wx:onAudioInterruptionEnd');
});
...
monitor.on('wx:onAudioInterruptionEnd', ()=> {
// 监听该事件,播放音乐
// 这里填空的话,会自动获取最后一次播放的音乐
wx.$audio.playBgm('', true);
}).on('muted:bgm', (muted)=> {
wx.$audio.muteBgm(muted);
}).on('muted:sound', (muted)=> {
wx.$audio.mute(muted);
});
路由监听
monitor
.on('scene:go', (name, opt = {}) => {
switch (name) {
case 'preload': {// 跳转到加载页
pointer = preload;
preload.show(opt);
break;
}
case 'home': {// 游戏首页
pointer = home;
home.show(opt);
break;
}
case 'game': {// 游戏页
pointer = game;
game.show(opt);
break;
}
}
});
// 刚进来,先去加载页
monitor.emit('scene:go', 'preload');
游戏开发
其他不做多介绍了,可自行查看源码了解。 这里介绍下游戏开发逻辑。
分解
考虑到后续的功能增加,这里先拆分游戏组件,分离出可复用可扩展的元素。 因为本游戏是控制小熊在方块上跳,小熊和障碍物都是出于各个方块上,因此,方块可拆分出来。
- Block类 所有方块的基类(也是空方块) 方块内有自己的container,用于存放道具,当然方块背景也是存放在container内,在游戏中就直接把container添加进游戏场景中即可,不需要知道方块的属性等。
class Block {
constructor(bg) {
this.uid = wx.$util.uuid(); // 每个方块都有属于自己的id
this.type = BLOCK_TYPE.EMPTY; // 方块的类型
this.sw = 0; // 方块的大小
this.sh = 0;
this._scale = 1;
this.container = new PIXI.Container();
this.bg = pixiUitl.genSprite(bg); // 方块图片
this.sw = this.bg.width;
this.sh = this.bg.height;
this.container.addChild(this.bg);
this.container.width = this.sw;
this.container.height = this.sh;
}
......
}
接着是具备道具功能的方块,继承与Block
/**
* 有障碍物的方块
*/
class BarrireBlock extends Block {
constructor(bg) {
super(bg);
this.type = BLOCK_TYPE.BARRIER;
// 在方块上放置障碍物
this.barrier = new PIXI.Sprite();
this.barrier.anchor.set(0.5, 0.5);
this.barrier.x = this.container.width / 2;
this.barrier.y = 18;
this.container.addChild(this.barrier);
}
setBarrier(name) {
// 设置障碍物图片
this.barrier.texture = pixiUitl.getTexture(name);
return this;
}
hideBarrier() {
// 清空障碍物
this.barrier.texture = null;
}
}
/**
* 假的方块(踩上去0.5秒后就会掉落)
*/
class FakeBlock extends Block {
constructor(bg) {
super(bg);
this.type = BLOCK_TYPE.FAKE;
this.waitToFall = false;
}
init() {
this.waitToFall = false;
}
startToFall() {
this.waitToFall = false;
}
fall() {
// 假的方块,被踩中后会自动掉落
return new Promise(resolve=> {
if (this.waitToFall) {
tween({
from: {
y: this.y,
alpha: 1
},
to: {
y: screen.height,
alpha: 0
},
ease: easing.easeIn,
duration: 200,
}).start({
update: v => {
this.y = v.y;
this.alpha = v.alpha;
},
complete: resolve
});
} else {
resolve();
}
});
}
}
-
线 有了方块,还需要一个统一管理每一行的方块的类,因为每一行的方块都是同时出现,超过一定行数后是同时消失。 所以做多一个Line类来管理。
-
池 因为该游戏会频繁生成方块,所以做多一个方块池来管理,每次创建都从该池中获取方块,如果不存在,则再新建。方块消失时自动放回池里待用。
-
熊 熊也跟方块类似,需要一个container,因为熊身上可能会有一些道具效果(比如保护圈)。
方块生成规则
好了,拆分好了这些类,接下来就是如何生成方块(如何放置障碍物等,不能出现必死的道路) 这里是从底部往上,一行一行的生成,前几行固定生成没有任何功能的方块。 前几行的方块数量由listData控制 const lineData = [1, 2, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4]; 之后的就每一行比上一行随机+-一个方块。
接下来就是重点,如何保证不会出现必死的道路。 需要通过上一行的方块功能来判断,可能比较复杂,讲不清晰,我自己能脑补画面,懒得画图了。。。
1、如果当前方块的前一个方块是障碍物,则该位置也不允许出现障碍物。 2、如果上一行比当前行数量多 2.1、上一行于当前方块相邻的两个方块如果都不是障碍物,那么该位置就可以随机出现障碍物,否则当前方块不允许出现障碍物。 2.2、当前方块是当前行的最后一个,且上一行的最后一个是障碍物,那该位置也可以是随机出现障碍物。 3、如果上一行比当前行数量少 3.1、当前行第一个和最后一个都可以是随机出现障碍物,最后一个可出现的前提是前一个非障碍物。 3.2、上一行相同下标位置是障碍物,那该行该下标的方块也可以是随机出现障碍物。
控制障碍物的数量,随着行数增加而增加: Math.random() < (0.4 + this.lineNum * 0.005)
小熊跳跃
小熊跳跃如何获取下一个方块,全局存放一个小熊所在方块在所在行的下标index。如果向左跳,则获取下一行的index-1位置的方块,否则获取index+1位置的方块。
如果方块不存在或者是障碍物,则判断游戏失败。
否则根据该方块的xy坐标计算小熊需要跳跃的位置。这里需要注意的是,没跳跃一层,所有方块就会下降一层,所以最终小熊跳跃停止后的y值是不变的。
子域绘制
需要注意的是,在子域绘制完排行榜等数据后,在主域绘制方法如下:
ticker.add(this.update, this);
update() {
if (this.showEnding) {
// 显示结束分数
canvas = wx.$open.getCanvas();
this.endSprite.texture = PIXI.Texture.from(canvas);
this.endSprite.texture.update();
}
}
最后
大概就分享这么多。