微信小游戏《成功拼拼》上线,分享项目及排行榜代码

====== 成语拼拼 ======

最近闲来无聊,业余时间写了个小游戏玩下。

从写demo到申请软著,网上找了个650块的,要30天工作日,等了一个多月吧。不过等等也没关系,反正玩嘛。

看论坛里有些码友说淘宝上可以找到更便宜的,时间也差不多。再一次感叹万能的淘宝呀。

中间正好有时间再改改游戏,等软件著下来就直接上线。

先上个小程序码。

分享一小套自家写的canvas组件,可以用来画排行榜。

先把代码贴出来

class View {
	constructor() {
		this.x = 0;
		this.y = 0;
		this.width = 0;
		this.height = 0;
		this._color = "#ffffff";
		this.zorder = 0;
		this.children = [];
		this.childMaxZorder = 0;
		this.render = null;
		this.parent = null;
		this.marginTop = 0;
		this.marginLeft = 0;
	}
	color (value) {
		this._color = value;
		return this;
	}
	size(width, height) {
		this.width = width;
		this.height = height;
		return this;
	}
	add(view) {
		view.parent = this;
		this.children.push(view);
		this.childMaxZorder++;
		view.zorder = this.childMaxZorder;
		return this;
	}
	addTo (container) {
		container.add(this);
		return this;
	}
	move(x, y) {
		this.x = x;
		this.y = y;
		return this;
	}
	remove(view) {
		for (var i = 0; i < this.children.length; i++) {
			if (this.children[i] == view) {
				this.children.splice(i, 1);
			}
		}
		return this;
	}
	draw(ctx, x, y) { }
}

这是个基础的组件,渲染组件跟render都是继承他的。

class Render extends View {
	constructor() {
		super();
		this.canvas = wx.getSharedCanvas();
		this.ctx = this.canvas.getContext('2d');
		this.width = 1000;
		this.height = 1000;
	}
	startRender(parent) { 
		if (!parent) {
			this.render = this;
			this.size(this.canvas.width, this.canvas.height);
			this.ctx.clearRect(this.x, this.y, this.width, this.height);
		}
		var parent = parent || this;
		var children = parent.children;
		children.sort(function (a, b) { return a.zorder - b.zorder; });
		for (var i = 0; i < children.length; i++) {
			var view = children[i];
			view.marginTop = parent.marginTop + view.y;
			view.marginLeft = parent.marginLeft + view.x;
			view.render = this.render;
			view.draw(this.ctx, view.marginLeft, view.marginTop);
			if (view.children.length > 0) {
				this.startRender(view);
			}
		}
	}
}

Render是视图的根本,你可以看成是Stage。
当所有组件都设置好之后,调用startRender即可开始渲染。

class Image extends View {
	constructor() {
		super();
		this.lineWidth = 1;
		this.imageUrl = null;
		this.imageData = null;
		this.isImageLoading = false;
		this.fillType = 0; 	// 0区域,1框, 2图片
	}
	fillRect(width, height) {
		this.fillType = 0;
		this.width = width;
		this.height = height;
		return this;
	}
	drawRect(width, height) {
		this.fillType = 1;
		this.width = width;
		this.height = height;
		return this;
	}
	image(url) {
		this.fillType = 2;
		this.imageUrl = url;
		return this;
	}
	draw(ctx, x, y) {
		ctx.fillStyle = this._color;
		if (this.fillType == 0) {
			ctx.fillRect(x, y, this.width, this.height);
		}
		else if (this.fillType == 1) {
			var h = this.height, w = this.width, lw = this.lineWidth;
			ctx.fillRect(x, y, w - lw, lw);      // top
			ctx.fillRect(x + w - lw, y, lw, h);      // right
			ctx.fillRect(x, y + h - lw, w, lw);      // bottom
			ctx.fillRect(x, y, lw, h - lw);      // left 
		}
		else if (this.fillType == 2) {
			if (!!this.imageData && !this.imageData.complete) return;
			if (!!this.imageData && this.imageData.complete) {
				ctx.drawImage(this.imageData, x, y, this.width, this.height);
			}
			else if (!this.imageData) {
				this.isImageLoading = true;
				this.imageData = wx.createImage();
				this.imageData.src = this.imageUrl;
				this.imageData.onload = () => {
					this.isImageLoading = false;
					if (!!this.render) this.render.startRender();
				};
			}
		}
	}
} 

Image组件是用来加载图片,或者是画框框的
例如:

  • var render = new Render();
  • var image = new Image().addTo(render).image(“cocoscreator.png”);
  • render.startRender();
    即可完成一张图片的加载及渲染。

在代码里设定了加载完图片之后,自动调用startRender再重新画一次,所以不用担心图片的层次会有什么问题。

class Label extends View {
	constructor() {
		super();
		this._font = "Helvetica";
		this._fontSize = 20;
		this._textAlign = "center";
		this._baseLine = "middle";
		this._text = null;
	}
	fontSize(value) { this._fontSize = value; return this; }
	textAlign(value) { this._textAlign = value; return this; }
	baseLine(value) { this._baseLine = value; return this; }
	text(value) { this._text = value; return this; }
	font(value) { this._font = value; return this; }
	topleft () { this._textAlign = "left"; this._baseLine = "top"; return this; }
	draw(ctx, x, y) {
		if (!!this.text) {
			ctx.fillStyle = this._color;
			ctx.textAlign = this._textAlign;
			ctx.textBaseline = this._baseLine;
			ctx.font = `${this._fontSize}px ${this._font}`;
			ctx.fillText(this._text, x, y);
		}
	}
}

这个是个Label组件,顾名思义,渲染文本用的。

下面把我小游戏里面用到的两个实例贴出来

class RankListHandler {
	constructor() {
		this.render = new Render();
		this.selfInfo = null;
		this.inited = false;
		this.keys = {
			ScoreKey: "levelScore"
		};
		this.users = [];
		this.friends = [];
		this.viewData = null;

		this.friend([this.keys.ScoreKey + 0]).then( (res) => {
			this.friends = res.data;
		} );

		this.info(["selfOpenId"]).then( data => {
			this.selfInfo = data[0];
			this.inited = true;
			this.showMT(this.viewData);
		} );
	}

	user (keylist) {
		return new Promise( (solve, reject) => {
			wx.getUserCloudStorage({
				keyList: keylist,
				success: res => {
					console.log("wx.getFriendCloudStorage success", res);
					solve(res);
				},
				fail: res => {
					console.log("wx.getFriendCloudStorage fail", res);
					reject(res);
				},
			});
		} );
	}

	//取出所有好友数据 关卡得分
	friend (keylist) {
		return new Promise( (solve, reject) => {
			wx.getFriendCloudStorage({
				keyList: keylist,
				success: res => {
					console.log("wx.getFriendCloudStorage success", res);
					solve(res);
				},
				fail: res => {
					console.log("wx.getFriendCloudStorage fail", res);
					reject(res);
				},
			});
		} );
	}

	info (idlist) {
		return new Promise( (solve, reject) => {
			wx.getUserInfo({
				openIdList: idlist,
				lang: 'zh_CN',
				success: res => {
					console.log("wx.getUserInfo success", res);
					var data = res.data;
					for (var i = 0; i < data.length; i++) {
						var info = data[i];
						for (var j = this.users.length - 1; j >= 0; j--) {
							if(this.users[j].openId == info.openId) {
								this.users.splice(j, 1);
								break;
							}
						}
						this.users.push(info);
					}
					solve(res.data);
				},
				fail: res => {
					console.log("wx.getUserInfo fail", res);
					reject(res);
				},
			});
		} );
	}

	getValueByKey (item, key) {
		var valist = item.KVDataList;
		for (var i = 0; i < valist.length; i++) {
			var info = valist[i];
			if(info.key == key) {
				return info.value;
			}
		}
		return 0;
	}
	showNear (data) {
		var width = this.sharedCanvas.width,
			height = this.sharedCanvas.height,
			padding = 10,
			border = 3,
			titleHeight = 60,
			key = data.key;

		this.drawBG(width, height, padding, border, titleHeight);

		this.friends.sort((a, b) => {
			return parseInt(this.getValueByKey(b, key)) - parseInt(this.getValueByKey(a, key));
		});
		var idx = 0, itemList = [];
		for (var i = 0; i < this.friends.length; i++) {
			var info = this.friends[i];
			if(info.avatarUrl == this.selfInfo.avatarUrl) {
				idx = i;
			}
		}
		for (var i = 0; i < 3; i++) {
			itemList.push(new View().move(padding + border + (width - padding * 2) * 0.333 * i, padding + 60).addTo(this.render));
			itemList[i].size((width - padding * 2) * 0.333 - border * 2, height - titleHeight - (padding) * 2 - border);
		}


		if(idx > 0) this.drawItem(itemList[0], this.friends[idx - 1], idx - 1, key, "#ffffff");
		this.drawItem(itemList[1], this.friends[idx], idx, key, "#00ff00");
		if(idx + 1 < this.friends.length) this.drawItem(itemList[2], this.friends[idx + 1], idx + 1, key, "#ffffff");

		this.render.startRender();
	}
	drawItem (container, info, index, key, color) {
		log("drawItem", index);
		// index
		new Label().text(index + 1).move(container.width / 2, 30).color(color).fontSize(36).addTo(container);

		// name
		new Label().text(info.nickname).move(container.width / 2, 150).color(color).fontSize(26).addTo(container);

		// 分数
		new Label().text(this.getValueByKey(info, key)).move(container.width / 2, 190).color(color).fontSize(28).addTo(container);

		// 底框 
		var picBg = new Image().color("#ffffff").move(container.width / 2 - 35, 52).fillRect(70, 70).addTo(container);

		// avatar
		var head = new Image().image(info.avatarUrl).move(5, 5).size(60, 60).addTo(picBg);

	}
	drawBG (width, height, padding, border, titleHeight) {
		// 底色
		var di = new Image().fillRect(width, height).color("#6972fe").addTo(this.render);

		// 外框
		new Image().move(padding, padding).color("#93bff7").drawRect(width - padding * 2, height - padding * 2).addTo(di).lineWidth = border;

		// 中间色块
		var midColor = new Image().color("#3a41bc").move(padding + border + (width - padding * 2) * 0.333, padding + 60).fillRect((width - padding * 2) * 0.333 - border * 2, height - titleHeight - (padding) * 2 - border).addTo(this.render);

		// 内框
		new Image().color("#93bff7").move(padding + (width - padding * 2) * 0.333, padding + titleHeight - border).drawRect((width - padding * 2) * 0.333, height - titleHeight - padding * 2 + border).addTo(di).lineWidth = border;

		// 标题框
		new Image().color("#93bff7").move(padding, padding).drawRect(width - padding * 2, titleHeight).addTo(di).lineWidth = border;

		new Label().move(20, 25).text("好友排行榜").baseLine("top").textAlign("left").fontSize(30).addTo(di);
	}

	showHead() {
		var width = this.sharedCanvas.width,
		height = this.sharedCanvas.height;

		// 底框 
		var picBg = new Image().color("#ffffff").move(10, 10).fillRect(80, 80).addTo(this.render);
		new Image().image(this.selfInfo.avatarUrl).move(5, 5).size(70, 70).addTo(picBg);
		new Label().text(this.selfInfo.nickName).color("#F1C100").topleft().move(110, 20).fontSize(30).addTo(this.render); 
		var sex = this.selfInfo.gender == 1 ? "帅哥" : "美女";
		new Label().text(`欢迎您,${sex}`).topleft().move(110, 60).fontSize(24).addTo(this.render);
		this.render.startRender();
	}

	getSelfInfo () {
		for (var i = 0; i < this.friends.length; i++) {
			var info = this.friends[i];
			if(info.avatarUrl == this.selfInfo.avatarUrl) {
				return info;
			}
		}
		return null;
	}

	setScore (data) {
		var info = this.getSelfInfo();
		if(!!info) {
			for (var i = 0; i < data.data.length; i++) {
				var item = data.data[i],
					exists = false;
				for (var i = 0; i < info.KVDataList.length; i++) {
					if(info.KVDataList[i].key == item.key) {
						if(parseInt(info.KVDataList[i].value) < parseInt(item.value)) {
							info.KVDataList[i].value = item.value;
						}
						exists = true;
						break;
					}
				}
				if(!exists) {
					info.KVDataList.push({key:item.key,value:parseInt(item.value)});
				}
			}
			for (var i = 0; i < info.KVDataList.length; i++) {
				var item = info.KVDataList[i];

			}
		}
		else {
			info = {};
			info.avatarUrl = this.selfInfo.avatarUrl;
			info.nickname = this.selfInfo.nickName;
			info.openid = this.selfInfo.openId;
			info.KVDataList = [];
			for (var i = 0; i < data.data.length; i++) {
				var item = data.data[i];
				info.KVDataList.push({key:item.key,value:item.value});
			}
			this.friends.push(info);
		}
	}

	start() {
		this.sharedCanvas = wx.getSharedCanvas();
		this.sharedCtx = this.sharedCanvas.getContext('2d');

		wx.onMessage(data => {
			log(data);
			if(!!data && data.mt != undefined) {
				this.viewData = data;
				if(this.inited) {
					this.showMT(data);
				}
			} 
		});
	}

	showMT(data) {
		if(!data) return;
		var messageType = data.mt;
		log("showmt", messageType);
		switch (messageType) {
			case mt.init:
				break;
			case mt.head:
				this.showHead();
				break;
			case mt.all:
				break;
			case mt.near:
				this.showNear(data);
				break;
			case mt.score:
				this.setScore(data);
				break;
		}
	}

}
new RankListHandler().start();

这里面有两个例子

一个是开始界面左上角那个欢迎您,帅哥,还带你的头像及名字

另外一个是结束界面那里的三人排行榜。

完整的好友排行榜大家可以自己玩玩。写这个组件的时候,我还是按方便使用的习惯来写的,用起来有点像cocos本身的语法,还带了点好久前用jquery的那种多方法连调的特性。

完整的代码在这下载。 index.zip (3.3 KB)

有兴趣可以留个言,希望对大家有帮助。

fourletters.rar (1.0 MB)
项目源代码在这里下载。
只打包了assets目录,要玩的同学建个空项目之后,解压压缩包,覆盖即可。
open目录是排行榜的代码,如上面所说,写了个小框架。体积会小很多。
还有个bat命令,是用来生成微信小程序之后,运行它就可以把开发数据域的代码,跟分享要用的图片拷到小游戏项目里面。

11赞

http://t.cn/EvFjR5C 免费收录微信小游戏,

:joy:还有这种操作?

那么贵, 我的软著才三百多一点.

自己申请的吗?还是三方?三方求分享下。

找代理申请的.

:grin: 分享下代理的联系方式?

pm一下代理联系方式,谢谢

可以分享下完整的项目源码学习下吗

1赞

可以,回头我打个包传上来。

fourletters.rar (1.0 MB)
项目源代码在这里下载。
只打包了assets目录,要玩的同学建个空项目之后,解压压缩包,覆盖即可。
open目录是排行榜的代码,如上面所说,写了个小框架。体积会小很多。
还有个bat命令,是用来生成微信小程序之后,运行它就可以把开发数据域的代码,跟分享要用的图片拷到小游戏项目里面。

skr skr
看了几个原生的排行榜实现,感觉这个是最接近我想要的:灵活,方便。
第一遍看的时候没理解View 的 marginTop marginLeft 。
后来看其他的实现的时候想着自己写一个类似html自动流布局的时候,才回忆起来这里的marginTop marginLeft 。虽然没有达到自动流布局但是也算简单易用灵活了,好吧先用楼主的啦。
最后感谢楼主的分享:kissing_heart:

谢谢支持哈,我自己也想要那种方便,然后就轻量实现了。很开心能帮得上。

很好玩的游戏啊,喜欢

你这个在哪个版本写的,能告知一下吗?是1.9还是2.0的。

mark

1.10.1。本来想换2.x的,结果前面几个版本用着还有bug。就先用着1.10在做。

:grin: 更新了~更新了~增加了少儿模式,青年模式跟老年模式
原来的模式变成中年模式,成语没有任何的提示,基础有10秒时间可以答题,先答对一道题,增加3秒剩余时间。
少儿模式,每次会先随便3个成语,然后依次提示完之后,才开始答题,基础时间也是有10秒,每答对一题,增加3秒剩余时间。
青年模式跟少儿模式相似,只是要记的成语数量变成了10个。
以上的模式都有限定时间的。
老年模式就没有了,没有提示,没有时间限制。合适年纪大的朋友慢慢玩。。
四种模式,总有一种适合你!:14:

代理的联系方式给一下。。

技术厉害又有分享精神,实在难得