pixi.js开发微信小游戏-小熊上山

8,928 阅读6分钟

第一个微信小游戏。哈哈又是第一个。因为此次疫情,宅在家里,不再找点事来做就真的快发霉了,所以就有了这个小游戏。

开源

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();
    }
}

最后

大概就分享这么多。

原文地址

codebear.cn/article?id=…