首先,我们来看下h5游戏和微信小游戏之间的区别:
1.我认为最大的一点区别在于,微信小游戏全是基于单个canvas的结构,而h5的游戏,我们可以使用多层的canvas对游戏进行分类,比如我之前有开发一个《大头吃小头》的h5游戏,如下图,演示地址:http://www.lovewebgames.com
在这里,我们就对它进行了三层结构的划分,1层为主游戏层,主要处理游戏内的各精灵的活动,1层为控制器层(或操作层),主要处理事件或摇杠的操作,第3层就是显示层了,主要展示一些文字如得分、生命值等信息。这样划分的好处是显而意见的,可以充分利用每层的独立渲染,减少性能的开销。然而在微信小游戏中,只有一个canvas是显示的,再创建的canvas都是不可见的。
2.第二点不同在于,无法使用dom元素,dom虽然在动画的性能上比canvas差很多,但它也有很多的优点,比如他可以结合css样式快速的呈现很好的展示效果,包括事件的处理也大不同,例如在《大头吃小头》这款游戏中,控制杠就是用html元素展示的,包括游戏里的一些按钮,它们并不是在每帧都需要重绘的,更多的可能是位置的变化。而在微信小游戏的开发中,我们完全不能使用除canvas外的html元素,游戏里的任何变化,只能通过每帧的清屏重绘来完成。
3.第三点不同在于api不同,基本上原来在浏览器端的api都不能用,包括事件的绑定也不一样,这也就是别人说的,小程序的开发,并不会给你前端的开发加成,因为是两套完全不同的api玩法,所以我们需要adapter,官方有提供一个简单的weapp-adapter,来综合小游戏和h5之间的差异,在下面的内容里,我将抛弃它,完全使用api来操作,既然是两套不同的玩法,我认为没必要统一。
上面说到adapter,是不是意味着我们要写很多的基础代码呢,当然要写,不过我已经把这些基础的代码写成了一个库,名字叫wx-jy,它的作用与adapter又有着不同,主要是为了我们减少对游戏的转承启合而开发代码,多的不说了,源码在github上有,包括h5版的jy和微信版的wx-jy
下面我们来看一下,今天我们要带大家完成的小游戏《打气球》,这款游戏很适合做入门教程,首先他包含有小游戏的所有过程,然后他逻辑简单,不会看不懂。说到游戏的过程,我把它分成六大状态:
前面三个状态loading、title、descript为游戏准备阶段,runing就是游戏的核心运行状态了,也许你会问了,为啥要分这么细,游戏不就是开始和结束吗?呃,那是你觉得,我要我觉得。
说了这么多了,我们还是没开始写《打气球》的一行代码,不要紧,老弟,磨材不误砍刀工,其实打气球的代码不超过十行,我们总不能拿那十行代码讲一天吧?好了,开始正式的撸码吧!
第一步,我们要创建一个游戏的舞台,我们所有的游戏小伙伴们都将在这个舞台上起舞。
const canvas = wx.createCanvas(); const [height, width] = [canvas.height, canvas.width] //创建舞台 let stage = new Stage(canvas, width, height, ‘#FFFFFF‘);
前两行引用wx-jy的相关依赖,你可以把这个文件单独下载下来引用。下面就是new Stage了,有了舞台后,我们就需要把一些要素放进去了,比如标题和介绍
let title = new Title(‘打气球‘, stage); title.create = (resolve) => { lib.write(stage, ‘一起来打气球‘) resolve(); } let descript = new Descript(stage) descript.create = async (resolve) => { lib.draw(stage, ‘images/descript.jpg‘, 0, 0, stage.width, stage.height) // await lib.waitMoment(3000); //添加开始按钮的Sprite let btn = new Sprite(stage, ‘images/btn-start.png‘, 100, 40, (width - 100) / 2, height - 40 - 40); btn.draw(); wx.onTouchStart((e) => { btn.touchHits(e,()=>{ wx.offTouchStart(); resolve() }) }); }
在descript这里的代码为什么会多一点,主要是要展示一个按钮,然后绑定点击的事件,所有步聚的方法都使用异步函数的方式编写,这样只需要resolve()调用就会自动走到下一步了。
接下来就是声明一个气球的精灵类了,这个精灵,有两个属性方法,一个是上升的速度,一个是更新位置的方法。
//气球类 class Ball extends Sprite { speed: number = 1;//速度 //更新位置 update() { this.y -= this.speed; if (this.y+this.height < 0) { this.visible = false; } } }
做完这些,我们就可以写我们的主体函数类了,它继承于JY,对过程进行扩展。
class Game extends JY { frame: number = 0;//帧数 ballList: Ball[] = [];//所有球的集合 newGame() { this.stage.style = "green"; this.setState(STATE.running); //事件绑定 wx.onTouchStart(e => { let { clientX, clientY } = e; console.log(clientX, clientY) this.ballList.forEach((ball,index) => { //触碰回收球并播放声音 ball.touchHits(e,()=>{ this.ballList.splice(index,1); lib.play(‘audio/boom.mp3‘); }) }); }); } async running() { this.frame++; //先清空场景 this.stage.clear(); this.createSprite(); this.ballList.forEach((ball,index) => { ball.update(); ball.draw(); //回收球 if(!ball.visible){ this.ballList.splice(index,1); } }); } //创建角色 createSprite() { //100帧创建一个角色 if (this.frame % 100 == 0) { let x = lib.random(0, stage.width - 30); let w = 40; let h = 340 / 120 * w; let ball = new Ball(this.stage, ‘images/ball.png‘, w, h, x, this.stage.height); // ball.touchHits(this.touch) this.ballList.push(ball); } } async gameOver() { stage.clear(); lib.write(stage, ‘游戏结束!‘); await lib.waitMoment(3000); this.setState(STATE.descript); } }
这里我们重写了newGame、runing和gameover这三个生命周期,目的是个性化内容,所有的游戏可能真正的区别也只是游戏开始、运行和结束三个状态是不一样的了。
最后,我们实例化这个类,然后启动它,还有资源文件的加载。
let mygame = new Game(stage, title, descript); mygame.resources = [ ‘images/ball.png‘, ‘images/btn-start.png‘, ‘images/descript.jpg‘, ‘audio/boom.mp3‘ ]; mygame.setup()
就这样,一款好看又好玩的《打气球》就基本完成了,所有需要我们编写的代码不过100行,中间你还可以添加生命数和分值来增强游戏的可玩性。
你也可以从github中clone下整个项目,然后用微信调试工具导入dev这个目录,就可以直接预览到效果了