这是一个微信小程序项目,是类似开心消消乐的休闲小游戏,老少皆宜,游戏互动里面的图片是用的任何图片素材,根据自己的需求更换图片即可。想要做游戏不知道怎么做,建议从这个小程序入手,花时间研究学习,很快就拥有属于自己的小程序。
准备
- 会使用微信开发工具
- 有游戏图片素材
- 有游戏背景音效
打开微信开发工具,选择一个小程序项目,点+号新建,依次选择
创建小程序
- 不使用云服务
- JavaScript - 基础模板
修改项目名,点确定,就可以开始做了
页面布局
首先,开始做项目的时候,设计稿或者效果图是准备好的,这样让我们知道下一步该怎么做
效果图
来看一看效果图,要做的游戏页面是像下面这样的
布局
有目标,加油干!
这里做页面是用基础模板template
来做的,有vue
基础的就很容易完成,
在页面文件pages/game/game.wxml
中的添加布局,大致如下
<view class="container">
<view class="row">
<view class="expand">
<view class="row">
<image src="{{firstImg}}" class="icon" mode="scaleToFill" />
<view>
<text>×{{scope}}</text>
</view>
</view>
</view>
<view class="" style="width: 28vw;">
<view class="row">
<image src="/static/five_oclock_3d.png" class="icon" mode="scaleToFill"/>
<view>
<text>{{timerNum}}s</text>
</view>
</view>
</view>
</view>
<view class="expand">
<view class="canvas-box">
<canvas class="canvas" id="canv" type="2d" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd"></canvas>
</view>
</view>
<progress percent="{{progressPercent}}" activeColor="#38f" backgroundColor="#ccc" />
</view>
还有样式文件,这里不展开讲,自己知道怎么改样式,做成效果图一样的就好了
效果图中的弹出对话框,这是小程序系统自带的,做的时候忽略掉
以上页面的布局中,有两个部分
- 一个画布
canvas
,显示游戏画面 - 一个显示游戏状态信息,
还有进度条
游戏逻辑
接下来,就写写游戏逻辑了,整理一下游戏思路,比如做一份流程图,把思路现出来
流程图
一个流程图出来了,如下所示,这下思路变清晰了吧
大纲
看一下大纲,就能知道大概的游戏逻辑吧,
游戏的相关配置如下,是可以调节的
const MatchCount = 3;//达到3个可消除
const AnimationTime = 10;//动画延迟10ms
const isShowClearLine = false;//圈出欲消除的图片
const maxTimeNum = 300;//倒计时最大值
const ColNum = 7;//列数,数字越大装得图片越多
逻辑代码,都写在一个页面文件pages/game/game.js
中,方便阅读,代码如下
Page({
/**
* 页面的初始数据
*/
data: {
firstImg:"",//第一个图片
progressPercent:100,//进度条进度
timerNum:maxTimeNum,//计数
scope:0,//消除数量
imgList:[],//存放游戏图片
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options){
//...这里处理初始化
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
//关闭定时器
if(this.data.timer){
clearInterval(this.data.timer);
}
//销毁游戏背景音
if(this.data.audioPlay && this.data.audioPlay.destroy){
this.data.audioPlay.destroy();
}
},
onTouchStart(e){
//...用户触摸时,会触发这个事件方法
//就在这里处理用户选图片的逻辑
},
onTouchEnd(){
//...用户不触摸时,会触发这个事件
},
showGameoverModal(){
//...游戏结束,计算得分,用对话框提示
},
initCanvasData(canvasData){
//...初始化画布数据,用于绘制游戏图
},
clearGrids(callback){
//...处理消除逻辑,包括消除动画,处理完成后回调callback
},
//...剩下的方法省略
})
在方法onLoad(options)
这里,写初始化逻辑,代码如下
//根据id获取到画布的节点node和大小size
wx.createSelectorQuery().select('#canv').fields({ node:true, size:true },res=>{
const { width, height, node:canvas } = res;
const canvasData = {
canvas,//画布元素(节点)
context: canvas.getContext('2d'),//获取画布的操作对象(上下文)
columns: ColNum,//每行的个数,设置在6~12之间最佳
};
//这是一些图片名字
const ImageList = [
//...更多图片资源
"deer","chicken","chipmunk","cow_face","crab",
];
//加入微任务列表
let taskList = ImageList.map(function(filename){
//加载图片是异步操作的
return new Promise(function(resolve,reject){
let image = canvas.createImage();
//...省略更多
//加载项目下static文件夹里面的图片资源
image.src=`/static/${filename}_3d.png`;
});
});
//执行微任务
Promise.all(taskList).then(imgs=>{
//执行到这里,微任务执行结束,就显示第一个图片
if(!this.data.firstImg) {
this.setData({
firstImg:'../../'+imgs[0].src
})
}
this.data.imgList = imgs;
this.initCanvasData(canvasData);
this.initAudioPlay();//初始化游戏音效
//...省略更多,
//开始执行消除处理动画的方法
this.clearGrids(()=>{
//...省略更多
//消除完了,开始倒计时
this.data.timer = setInterval(()=>{
let num = this.data.timerNum-1;
if (num<0){
//倒计时结束了
clearInterval(this.data.timer);
this.data.timer = null;
//游戏结束,弹出对话框提示
this.showGameoverModal();
return;
}
// 更新倒计时和进度条
this.setData({
timerNum:num,
progressPercent:Math.trunc(num*100/maxTimeNum)
})
},1000);
});
}).catch(function(err){
//如果执行有问题,会将错误输出到控制台
console.error(err)
});
//...
}).exec();
代码中有调用的其它方法,它们不是重要的,这里不展开讲,
如果你对这个Promise
感到陌生,它是处理异步(微)任务的,建议你熟悉 Promise,相信这对你很有帮助
初始化数据
在这个方法initCanvasData(canvasData)
里去实现初始化,参数canvasData
表示画布数据,很简单,代码如下
let padding = 1;//初始内边距
const bgColor = '#000';//背景色
const lineColor = '#ff3';//选中边框色
//获取初始化数据对象
const { canvas } = this.data.canvasData;
const { width, height } = canvas;
padding += Math.trunc((width-padding*2)%columns/2);
//根据画布宽高,算出单元格大小
const gridSize = Math.trunc((width-padding*2)/columns);
//算出有多少行
const rows = Math.trunc((height-padding*2)/gridSize);
//网格列表
const gridList = [];
for(let row=0; row<rows; row++){
for(let col=0; col<columns; col++){
let grid = {
//...
};
//获取随机的图片(索引)
grid.font = this.getRandomFont();
//加入列表
gridList.push(grid);
}
}
//所有初始化数据放到canvasData中
Object.assign(canvasData, { bgColor, lineColor, gridList, gridSize, padding, columns, rows });
this.data.canvasData = canvasData;
其中方法getRandomFont()
是获取随机的图片,返回图片列表中的索引即可,代码如下,只写一行足矣
return Math.trunc(Math.random()*this.data.imgList.length);
重复写代码是低效率的,要这样做,将重复的代码块放进一个方法中,后面有很多地方会调用到
处理用户选图片
这应该不难吧,在这个方法onTouchStart(e)
中去处理,参数e
是用户触摸画布时由系统处理传入的,代码如下
//触摸时,获取第一个坐标点对象
const t = e.touches[0];
const { selectedGrid, isAnimating } = this.data;
//如果在进行动画,就直接返回,不继续执行
if(isAnimating) return;
const { context, canvas, gridList, gridSize, bgImg } = this.data.canvasData;
//获取触摸到的图片索引
let gIndex = this.getTouchGridIndex(t);
//若触摸的不是图片,就直接返回
if(gIndex<0) return;
//开始清理画布
this.clearCanvas(true);
context.drawImage(bgImg,0,0,canvas.width,canvas.height);
//选中图片,画出选中小效果
let grid = gridList[gIndex];
//获取图片的坐标
let coord = this.getGridCoordinate(grid);
context.rect(coord.left,coord.top,gridSize,gridSize);
context.stroke();
//将选中的图片存到起
let newSelectedGrid = {
//...
};
if (selectedGrid) {
this.data.selectedGrid = null;
//选到了两个图片,这里开始处理切换动画
this.startToggleAnimation(selectedGrid,newSelectedGrid);
}else{
this.data.selectedGrid = newSelectedGrid;
}
其中,两个方法
getTouchGridIndex(touch)
和getGridCoordinate(grid)
是很容易实现的,这里不展开讲,
另一个方法 startToggleAnimation(selectedGrid,nextSelectedGrid)
,是处理切换动画效果的,实现起来有难度,这里大致讲一下,代码如下
//...
//判断选的两个图片是否可以切换
const isToggle = this.calcIsClearUp(selectedGrid,nextSelectedGrid);
let hasEq;
//...省略判断逻辑
//如果可以消除
if(hasEq){
//...
//设置动画进行中状态
this.data.isAnimating=true;
const { canvas, context, gridSize, gridList, bgImg } = this.data.canvasData;
const { grids, direction } = hasEq;
//...
//动画结束方法
const stopAnimation = () => {
if(isToggle){
//...
}
//...
this.redrawBg(()=>{
//处理消除逻辑的方法
this.clearGrids(res=>{
const { count } = res;
if(count>0){
//更新消除结果
this.setData({
scope:this.data.scope+count,
timerNum:maxTimeNum,
progressPercent:100
})
}
});
});
}
function startAnimation(){
this.clearCanvas(true);
context.drawImage(bgImg,0,0,canvas.width,canvas.height);
//...省略了开始动画逻辑
setTimeout(startAnimation,AnimationTime*2);
}
//重新绘制背景的方法
this.redrawBg((img)=>{
//开始动画
startAnimation();
});
}
代码中还用到了好几个方法,这里就不展开讲,
代码中用到了好几个箭头符号,据说这个是叫语法糖,
考虑到有的萌新小同学看不懂,这里讲一下语法糖,
箭头符号表示什么意思,看如下代码,应该能明白吧
//箭头函数
const fun1 = (args) => {
console.log('hello')
};
//编译器把箭头函数还原
function fun1(args) {
console.log('hello')
}
也许有同学疑问:没看出什么用途。
在一个对象实例中,写方法(函数)里有写了this
的,就用箭头函数吧。
为方便阅读思路清晰,避免搞混,代码中用到的
this
,都是统一指向当前页面的实例对象
处理消除逻辑
在一个方法clearGrids(callback,count=0)
里实现,这是最难的实现部分,代码如下
const { gridList, gridSize, columns, rows, padding } = this.data.canvasData;
//先扫描可以清除的网格,记录下来
let clearGridsAtCols = [];
for(let x=0; x<columns; x++){
//...
for(let y=rows-1; y>=0; y--){
//...
}
//...
}
//如果有清除的网络
if(clearGridsAtCols.length>0) {
//这里添加需要移动的数据
clearGridsAtCols.forEach(item=>{
//...
for(let y=rows-1; y>=0; y--){
//...
}
});
const offset = 4;
//开始动画
const startAnimating=()=>{
let isDownMove=false;
//处理移动的数据,更新移动位置
clearGridsAtCols.forEach(x=>{
//...
});
//如果没有可以移动的,就结束动画
if(!isDownMove){
//...
this.redrawBg(()=>{
//... 处理完,回调返回消除数量
if(typeof callback == 'function') callback({ count });
});
return;
}
this.redrawBg();
//还能向下移动的,再调用startAnimating方法
setTimeout(startAnimating,AnimationTime);
}
//重新绘制一遍
this.redrawBg();
if(!this.data.isAnimating){
this.data.isAnimating=true;
this.data.audioPlay.play();//播放游戏音效
// console.log('start autio')
}
startAnimating();
return true;
}
//没有可消除的,直接返回状态
this.data.isAnimating=false;
return false;
关于项目
相关代码就讲到这了,有了上面的思路,应该能自己做出来吧,接下来是运行测试
运行效果
做好后,运行效果图如下,换个水果之类的图片,看着感觉还可以
为什么不用表情图片了?
有人体验后反馈,说玩久了眼睛看着会不舒服,
原因吧,是显示的图片又多又小,还有图片之间颜色相似的也多,找起来容易引起视觉疲劳,
使用表情图片大多数都是黄脸,所以颜色相似的很多,这是作为图形设计师的基本常识把,
知道了这点不足,就换了图片,看上面的感觉还好,
运行小程序项目,游戏交互效果,动图如下
文章来源:https://www.toymoban.com/news/detail-499504.html
既然能换图片,那就改名项目为图片消消乐好了文章来源地址https://www.toymoban.com/news/detail-499504.html
项目源码
- 用到的图片素材,是参考 fluent-emoji 这里,有很多可以选几个当作游戏素材,
- 用到的游戏背景音,也是从网上找来的,很容易找到,
- 想看项目源代码,请前往 下载点这里 找消消乐项目源码
到了这里,关于【图片消消乐】单机游戏-微信小程序项目开发入门的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!