一、实验目标
1、综合应用所学知识创建完整的拼图游戏项目;2、熟练掌握<canvas>组件。
二、实验步骤
1、创建小程序项目,选择不使用模版
2、初始化小程序文件
3,创建其他文件
4、视图设计
1)导航栏设计 navigationBarBackgroundColor 设置小程序导航栏背景色
{
"pages":[
"pages/index/index",
"pages/game/game"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#E64340",
"navigationBarTitleText": "拼图游戏",
"navigationBarTextStyle":"white"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
2)页面设计
1,公共样式设计:首先在app.wxss中设置页面窗口和顶端标题的公共样式
.container {
height: 100%;
color: #E64340;
font-weight: bold;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.title{
font-size: 18pt;
}
2,首页设计:
首页主要包含两部分内容,即标题和关卡列表。计划使用如下组件:
顶端标题:view容器;
关卡列表:view容器,内部使用数组循环。
<view class="container">
<!-- 标题 -->
<view class="title">游戏选关</view>
<!-- 关卡列表 -->
<view class="levelBox">
<view class="box" wx:for="{{levels}}" wx:key="id" bindtap="chooseLevel" data-level='{{item}}'>
<image src="/images/{{item}}"></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
/* 关卡列表区域 */
.levelBox{
width: 100%;
}
/* 单个关卡区域 */
.box{
width: 50%;
float: left;
margin: 25rpx 0;
display: flex;
flex-direction: column;
align-items: center;
}
image{
width: 260rpx;
height: 260rpx;
}
3,游戏页面设计:游戏页面需要用户点击首页的关卡列表,然后在新窗口中打开该页面口游戏页面包括游戏提示图、游戏画面和“重新开始”按钮。
由于暂时没有做点击跳转的逻辑设计,所以可以在开发工具顶端选择“普通编译”下的“添加编译模式”,并携带临时测试参数level=pic01.jpg。
此时预览就可以直接显示game页面了,设计完毕后再改回“普通编译”模式即可重新显示首页。
<view class="container">
<!-- 提示图区域 -->
<view class="title">提示图</view>
<image src="{{url}}"></image>
<!-- 游戏画布 -->
<canvas canvas-id="myCanvas" bindtouchstart="touchBox"></canvas>
<!-- “重新开始”按钮 -->
<button type="warn" bindtap='restartGame'>重新开始</button>
</view>
/* 提示图样式 */
image{
width: 250rpx;
height: 250rpx;
}
/* 画布样式 */
canvas{
border:1rpx solid;
width:300px;
height: 300px;
}
由图可见,此时可以显示完整样式效果。由于尚未获得游戏数据,所以暂时无法根据用户点击的关卡入口显示对应的游戏内容,仅供作为样式参考
5,逻辑实现
1)首页逻辑
1,关卡列表展示:在JS文件的 data 中录入关卡图片的数据信息,这里以6个关卡为例
data: {
levels:[
'pic01.jpg',
'pic02.jpg',
'pic03.jpg',
'pic04.jpg',
'pic05.jpg',
'pic06.jpg',
]
},
接着为关卡对应的<view>组件添加wx:for属性循环显示关卡列表数据和图片。
<view class="container">
<!-- 标题 -->
<view class="title">游戏选关</view>
<!-- 关卡列表 -->
<view class="levelBox">
<view class="box" wx:for="{{levels}}" wx:key="id" bindtap="chooseLevel" data-level='{{item}}'>
<image src="/images/{{item}}"></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
2,点击跳转游戏页面:若希望用户点击关卡图片即可实现跳转,需要首先为关卡列表项目添加点击事件
<view class="box" wx:for="{{levels}}" wx:key="id" bindtap="chooseLevel" data-level='{{item}}'>
上述代码表示为关卡添加了自定义点击事件函数chooseLevel,并且使用data--level属性携带了关卡图片信息。然后在对应的index.js文件中添加chooseLevel函数的内容,代码片段如下:
// 自定义函数--游戏选关
chooseLevel:function(e){
let level =e.currentTarget.dataset.level
wx.navigateTo({
url:'../game/game?level='+level
})
},
2)游戏页逻辑
1,显示提示图:在首页逻辑中已经实现了页面跳转并携带了关卡对应的图片信息,现在视频讲解需要在游戏页 面接收关卡信息,并显示对应的图片内容。
onLoad:function (options) {
// 更新图片路径地址
url='/images/'+options.level
// 更新提示图片的地址
this.setData({url:url})game? Level
}
<view class="container">
<!-- 提示图区域 -->
<view class="title">提示图</view>
<image src="{{url}}"></image>
此时重新从首页点击不同的关卡图片跳转就可以发现已经能够正确显示对应的内容了
2,游戏逻辑实现
准备工作。首先在game.js文件的顶端记录一些游戏初始数据信息。
var num=[
['00','01','02'],
['10','11','12'],
['20','21','22']
]
var w=100
var url='/images/pic01.jpg.png'
初始化拼图画面。传统做法是随机抽取画面中的任意两个方块,然后交换彼此位置,在进行足够多次数的交换后基本可以实现随机打乱的效果。但是这种方法有一个弊端,就是有时候会陷入无解的死局。因此可以考虑从空白方块的所在位置入手,每次随机让它和周围的邻近方块交换位置,这样可以通过方块反向移动回到最初始状态(确保本局有解),并且在交换足够多的次数后也可以实现随机打乱的效果。
shuffle:function(){
//先令所有方块回归初始位置
num=[
['00','01','02'],
['10','11','12'],
['20','21','22']
]
//记录当前空白方块的行和列
var row=2
var col=2
//打乱方块顺序100次
for(var i=0;i<100;i++){
// 随机产生其中一个方向:上(0),下(1),左(2),右(3)
var direction=Math.round(Math.random()*3)
// 上:0
if(direction==0){
//空白方块不在最上面一行
if(row!=0){
//交换位置
num[row][col]=num[row-1][col]
num[row-1][col]='22'
// 更新空白方块的行
row-=1
}
// 下:1
else if (direction==1){
// 空白方块不在最下面一行
if(row!=2){
// 交换位置
num[row][col]=num[row+1][col]
num[row+1][col]='22'
// 更新空白方块的行
row+=1
}
}
//左:2
else if (direction==2){
// 空白方块不在最左侧
if(col!=0){
// 交换位置
num[row][col]=num[row][col-1]
num[row][col-1]='22'
// 更新空白方块的行
col-=1
}
}
//右:3
else if (direction==3){
// 空白方块不在最下面一行
if(col!=2){
// 交换位置
num[row][col]=num[row][col+1]
num[row][col+1]='22'
// 更新空白方块的行
col+=1
}
}
}
}
},
上述代码表示使用for循环进行了100次打乱,开发者可以根据自己的需求更改循环次数。每次使用Math.random()方法从上下、左、右4个方向中随机产生一个方向,之后如果符合条件则交换空白方块和图片方块的位置。
然后在game.js中添加自定义函数drawCanvas,用于将打乱后的图片方块绘制到画布上。
drawCanvas:function(){
let ctx=this.ctx
//清空画布
ctx.clearRect(0,0,300,300)
// 使用双重for循环绘制3*3的拼图
for(var i=0;i<3;i++){
for(var j=0;j<3;j++){
if(num[i][j]!='22'){
// 获取行和列
var row=parseInt(num[i][j]/10)
var col=num[i][j]%10
// 绘制方块
ctx.drawImage(url,col*w,row*w,w,w,j*w,i*w,w,w)
}
}
}
ctx.draw()
},
最后在game.js的onLoad函数中调用自定义函数shuffle和drawCanvas
onLoad:function (options) {
// 更新图片路径地址
url='/images/'+options.level
// 更新提示图片的地址
this.setData({url:url})
//创建画布上下文
this.ctx=wx.createCanvasContext('myCanvas')
// 打乱方块顺序
this.shuffle()
// 绘制画布内容
this.drawCanvas()
},
3,移动被点击的方块。
修改game.wxml页面中的画布组件(<canvas>),为其绑定触摸事件。
<view class="container">
<!-- 提示图区域 -->
<view class="title">提示图</view>
<image src="{{url}}"></image>
<!-- 游戏画布 -->
<canvas canvas-id="myCanvas" bindtouchstart="touchBox"></canvas>
<!-- “重新开始”按钮 -->
<button type="warn" bindtap='restartGame'>重新开始</button>
</view>
在game.js文件添加自定义函数 touchBox,用于实现图片方块的移动,对应的JS
touchBox:function(e){
//如果游戏已经成功,不做任何操作
if(this.data.isWin){
//终止本函数
return
}
//获取被点击方块的坐标x和y
var x=e.changedTouches[0].x
var y=e.changedTouches[0].y
//console.log('x:'+x+',y:'+y)
//换算成行和列
var row=parseInt(y/w)
var col=parseInt(x/w)
// 如果点击的不是空白位置
if(num[row][col]!='22'){
// 尝试移动方块
this.moveBox(row,col)
// 重新绘制画布内容
this.drawCanvas()
// 判断游戏是否成功
if(this.isWin()){
// 在画面上绘制提示语句
let ctx=this.ctx
// 绘制完整图片
ctx.drawImage(url,0,0)
// 绘制文字
ctx.setFillStyle('#E64340')
ctx.setTextAlign('center')
ctx.setFontSize(60)
ctx.fillText('游戏成功',150,150)
ctx.draw()
}
}
},
// 自定义函数--移动被点击的方块
moveBox:function(i,j){
// 情况1:如果被点击的方块不在最上方,检查可否上移
if(i>0){
//如果方块上方是空白
if(num[i-1][j]=='22'){
//交换方块与空白的位置
num[i-1][j]=num[i][j]
num[i][j]='22'
return
}
}
// 情况2:如果被点击的方块不在最下方,检查可否下移
if(i<2){
//如果方块下方是空白
if(num[i+1][j]=='22'){
//交换方块与空白的位置
num[i+1][j]=num[i][j]
num[i][j]='22'
return
}
}
//情况3:如果被点击的方块不在最左边,检查可否左移
if(j>0){
//如果方块左边是空白
if(num[i][j-1]=='22'){
//交换方块与空白的位置
num[i][j-1]=num[i][j]
num[i][j]='22'
return
}
}
//情况4:如果被点击的方块不在最右边,检查可否右移
if(j<2){
//如果方块右边是空白
if(num[i][j+1]=='22'){
//交换方块与空白的位置
num[i][j+1]=num[i][j]
num[i][j]='22'
return
}
}
},
3,判断游戏成功
首先在game.js文件中的data中添加初始数据 isWin,用于标记游戏成功与否。对应的JS
data: {
isWin:false
},
isWin:function(){
// 使用双重for循环遍历整个数组
for(var i=0;i<3;i++){
for(var j=0;j<3;j++){
//如果有方块位置不对
if(num[i][j]!=i*10+j){
// 返回false,表示游戏尚未成功
return false
}
}
}
//更新游戏成功状态
this.setData({isWin:true})
// 返回true,表示游戏成功
return true
},
4,重新开始游戏
修改game.wxml代码,为“重新开始”按钮追加自定义函数的点击事件
<!-- “重新开始”按钮 -->
<button type="warn" bindtap='restartGame'>重新开始</button>
restartGame:function(){
//更新游戏成功状态
this.setData({isWin:false})
// 打乱方块顺序
this.shuffle()
// 绘制画布内容
this.drawCanvas()
},
三、程序运行结果
列出程序的最终运行结果及截图。
四、问题总结与体会
描述实验过程中所遇到的问题,以及是如何解决的。有哪些收获和体会,对于课程的安排有哪些建议。
1、发现无法弹出游戏成功的字样
改进:通过不断地尝试和查阅资料,发现没有更新touchBox函数。
2、收获:
-
设计页面模板:设计小程序的页面模板,包括页面的布局、样式和组件。可以使用小程序提供的组件库来构建页面,也可以自定义组件来满足特定需求。
-
页面模板与逻辑连接:要将页面模板和逻辑代码进行连接,以实现页面的渲染和交互。在小程序中,可以使用
Page
函数来定义页面对象,并在其中定义data
数据和各种生命周期函数(如onLoad
、onShow
等)来管理页面的状态和行为。 -
学会使用新组件 canvas,还有wx:for,并指定key,来实现for循环多条数据。
-
学会了初始化拼图画面,随机让它和周围的邻近方块交换位置,通过方块反向移动回到最初始状态,并且在交换足够多的次数后也可以实现随机打乱的效果。