微信小程序“别踩白块”源码分享
先看效果图(自己做小程序很简单,下面是具体实现)
首先,要了解微信小程序的开发工具——微信开发者工具
可以在微信公众平台下载软件
一、项目目录框架及配置
1.文件框架
(1)pages文件夹:
存放小程序的页面文件,书写各个页面代码以及组件,里面包括各种全局文件。
(2)app.js
小程序入口文件,用于定义全局数据和函数的使用,可以指定微信小程序的生命周期函数。
(3)app.json
全局文件, 对小程序进行配置,小程序的全局配置,小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等; 包括配置小程序是由哪些页面组成,配置小程序的窗口及背景色,配置导航条样式,配置默认标题等。
(4)app.wxss
全局的样式文件。
(5)app.wxml
构建页面结构
(6)project.config.json
保存开发工具配置项
(7)sitemap.json
网站地图,可以对小程序进行seo优化,让搜索排名靠前
(8)utills文件夹
存放全局的一些.js文件,公共用到的一些事件处理代码文件可以放到该文件夹下,用于全局调用。
以下是结构示图
2.项目配置
一个小程序最开始要使用app.json文件来对微信小程序进行全局配置,它决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等基础元素。
{
"pages": [
"pages/index/index",
"pages/endless/play",
"pages/time/play",
"pages/speed/play",
"pages/end/end",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "别踩白块",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json"
}
二、别踩白块结构分析
1. 模块功能:
无尽模式、计时模式、急速模式、查看日志
2. 文件配置
二、别踩白块各模块代码
1. 全局配置
(1)app.json
代码如下:
{
"pages": [
"pages/index/index",
"pages/endless/play",
"pages/time/play",
"pages/speed/play",
"pages/end/end",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "别踩白块",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json"
}
(2)app.js
代码如下:
//app.js
App({
onLaunch: function () {
//调用API从本地缓存中获取数据
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
getUserInfo:function(cb){
var that = this
if(this.globalData.userInfo){
typeof cb == "function" && cb(this.globalData.userInfo)
}else{
//调用登录接口
wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo = res.userInfo
typeof cb == "function" && cb(that.globalData.userInfo)
}
})
}
})
}
},
getHeighestScore: function(scoreName, cb){
this.globalData[scoreName] = wx.getStorageSync(scoreName);
typeof cb == "function" && cb(this.globalData[scoreName])
},
globalData:{
userInfo: null,
currentScore: 0,
endlessScore: null,
timeScore: null,
speedScore: null
}
})
(3)app.wxss
代码如下:
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
}
(4)project.config.json
代码如下:
{
"description": "项目配置文件",
"packOptions": {
"ignore": []
},
"setting": {
"urlCheck": true,
"es6": true,
"enhance": false,
"postcss": true,
"preloadBackgroundData": false,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"bundle": false,
"useIsolateContext": true,
"useCompilerModule": true,
"userConfirmedUseCompilerModuleSwitch": false,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true
},
"compileType": "miniprogram",
"libVersion": "2.16.1",
"appid": "wxd5512791eaec8564",
"projectname": "miniprogram-1",
"debugOptions": {
"hidedInDevtools": []
},
"scripts": {},
"isGameTourist": false,
"condition": {
"search": {
"list": []
},
"conversation": {
"list": []
},
"game": {
"list": []
},
"plugin": {
"list": []
},
"gamePlugin": {
"list": []
},
"miniprogram": {
"list": []
}
}
}
2.首页配置(index)
(1)index.js
代码如下:
//index.js
//获取应用实例
var app = getApp()
Page({
data: {
userInfo: {},
heighestScore: 0,
longestTime: 0
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
goGame: function(event){
var gameType = event.target.id;
wx.redirectTo({
url: '../'+gameType+'/play'
})
},
onLoad: function () {
var that = this
//调用应用实例的方法获取全局数据
app.getUserInfo(function(userInfo){
//更新数据
that.setData({
userInfo:userInfo
})
})
// 最高分数
app.getHeighestScore('endlessScore', function(heighestScore){
app.globalData.endlessScore = heighestScore || 0;
that.setData({
heighestScore: heighestScore || 0
})
});
// 最长时间
app.getHeighestScore('timeScore', function(heighestScore){
app.globalData.timeScore = heighestScore || 0;
that.setData({
longestTime: heighestScore || 0
})
});
}
})
(2)index.json
代码如下:
{}
(3)index.wxml
代码如下:
<!--index.wxml-->
<view class="container">
<view class="line">
<view class="blocks black" id="endless" bindtap="goGame">
<text>无尽模式</text>
</view>
<view class="blocks" id="time" bindtap="goGame">
<text>计时模式</text>
</view>
</view>
<view class="line">
<view class="blocks" id="speed" bindtap="goGame">
<text>急速模式</text>
</view>
<view class="blocks black" bindtap="bindViewTap">
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</view>
</view>
<view class="line">
<view class="blocks black">
<text>最高分数:{{heighestScore}}</text>
</view>
<view class="blocks">
<text>最长时间:{{longestTime}}</text>
</view>
</view>
</view>
(4)index.wxss
代码如下:
/**index.wxss**/
page {
height: 100%;
}
.line {
height: 33%;
display: flex;
flex-direction: row;
justify-content: center;
}
.blocks {
width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.black {
background: #000000;
}
.black text {
color: #ffffff;
}
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
}
.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.userinfo-nickname {
color: #aaa;
}
3. 无尽模式配置(endless)
(1)play.js
代码如下:
// play
var app = getApp()
Page({
data: {
typeName: '无尽模式',
silding: false,
score: 0,
blockData:[]
},
onReady: function(){
var array = [];
// 先生成一个10个长度的数组
for(var i = 0; i < 10; i++){
// 生成一个随机位数为1的数组
var orderArray = [0,0,0,0];
var randomNum = Math.floor(Math.random() * 4);
orderArray[randomNum] = 1;
array.push({id: i, block: orderArray});
}
this.setData({
blockData: array.reverse()
});
},
handleClick: function(events){
var id = events.currentTarget.id;
var line = id.split("-")[1];
var column = id.split("-")[2];
var isBlack = id.split("-")[3];
var blockData = this.data.blockData.reverse();
var score = this.data.score;
var orderArray = [0,0,0,0];
// 判断是否是第一行
if(line != blockData[0].id){
this.handleWrong(0, score);
return;
}
// 判断是否正确
if(isBlack != 1){
this.handleWrong(1, score);
return;
}
// 正确下一个
// 分数++
// 最后一个小块的id为分数+10
score++;
orderArray[Math.floor(Math.random() * 4)] = 1;
blockData.push({id: score+10, block: orderArray});
blockData.shift();
this.setData({
silding: true,
score: score,
blockData: blockData.reverse()
});
},
handleWrong: function( type , score){
const titleArr = ["请点击第一个白块!游戏结束", "别点白块!游戏结束"];
wx.showToast({
title: titleArr[type],
icon: 'cancel',
duration: 2000,
complete: function(){
// 将此分数存入全局变量
app.globalData.currentScore = score;
// 若此分数比最高分数还高 将其存入本地
if(score > app.globalData.endlessScore){
app.globalData.endlessScore = score;
wx.setStorageSync('endlessScore',score);
}
var timer = setTimeout(function(){
wx.redirectTo({
url: '../end/end?type=endless&score=' + score
})
clearTimeout(timer);
}, 2000);
}
})
},
onLoad: function(){
var that = this;
wx.setNavigationBarTitle({
title: that.data.typeName
});
}
})
(2)play.json
游戏模式中不需要全局配置
代码如下:
{}
(3)play.wxml
代码如下:
<!--play.wxml-->
<view class="score">{{score}}</view>
<view class="play-box">
<block wx:for="{{blockData}}" wx:for-index="i" wx:key="i">
<view class="block-line" id="line-{{blockData[i].id}}">
<block wx:for="{{blockData[i].block}}" wx:key="*this" wx:for-index="j">
<view wx:if="{{blockData[i].block[j] == 0}}" id="block-{{blockData[i].id}}-{{j}}-{{0}}" class="block" bindtap="handleClick"></view>
<view wx:else class="block black" id="block-{{blockData[i].id}}-{{j}}-{{1}}" bindtap="handleClick"></view>
</block>
</view>
</block>
</view>
(4)play.wxss
代码如下:
/*page {
display: flex;
flex-direction: column-reverse;
}*/
.score {
position: fixed;
top: 2rem;
left: 50%;
font-size: 3rem;
color: #fc2736;
transform: translate(-50%, 0);
z-index: 10;
}
.play-box {
width: 100%;
position: fixed;
left: 0;
bottom: 0;
}
.block-line {
width: 100%;
overflow: hidden;
}
.block {
width: 5rem;
height: 8rem;
float: left;
border: 1px solid #000000;
box-sizing: border-box;
}
.black {
background: #000000;
}
4. 计时模式配置(time)
(1)play.js
代码如下:
// play
var app = getApp()
Page({
data: {
typeName: '计时模式',
score: 0,
time: 60,
shouldStop: false,
blockData:[]
},
onReady: function(){
var array = [];
// 先生成一个10个长度的数组
for(var i = 0; i < 10; i++){
// 生成一个随机位数为1的数组
var orderArray = [0,0,0,0];
var randomNum = Math.floor(Math.random() * 4);
orderArray[randomNum] = 1;
array.push({id: i, block: orderArray});
}
this.setData({
blockData: array.reverse()
});
},
handleClick: function(events){
var id = events.currentTarget.id;
var line = id.split("-")[1];
var column = id.split("-")[2];
var isBlack = id.split("-")[3];
var blockData = this.data.blockData.reverse();
var score = this.data.score;
var orderArray = [0,0,0,0];
// 判断是否是第一行
if(line != blockData[0].id){
this.handleWrong(0, score);
return;
}
// 判断是否正确
if(isBlack != 1){
this.handleWrong(1, score);
return;
}
// 正确下一个
// 分数++
// 最后一个小块的id为分数+10
score++;
orderArray[Math.floor(Math.random() * 4)] = 1;
blockData.push({id: score+10, block: orderArray});
blockData.shift();
this.setData({
silding: true,
score: score,
blockData: blockData.reverse()
});
},
handleWrong: function( type , score){
const titleArr = ["请点击第一个白块!游戏结束", "别点白块!游戏结束", "时间到"];
var _this = this;
wx.showToast({
title: titleArr[type],
icon: 'cancel',
duration: 2000,
complete: function(){
// 将此分数存入全局变量
app.globalData.currentScore = score;
// 停止计数器
_this.setData({
shouldStop: true
});
// 若此分数比最高分数还高 将其存入本地
if(score > app.globalData.timeScore){
app.globalData.timeScore = score;
wx.setStorageSync('timeScore',score);
}
var timer = setTimeout(function(){
wx.redirectTo({
url: '../end/end?type=time&score=' + score
})
clearTimeout(timer);
}, 2000);
}
})
},
timeInterval: function(){
var that = this;
var timer = setInterval(function(){
// 判断是否小于0
var nowTime = that.data.time;
if(that.data.shouldStop){
clearInterval(timer);
}
if(nowTime > 1){
that.setData({
time: nowTime-1
});
return;
}
that.setData({
time: nowTime-1
});
that.handleWrong(2, that.data.score);
clearInterval(timer);
}, 1000);
},
onLoad: function(){
var that = this;
wx.setNavigationBarTitle({
title: that.data.typeName
});
this.timeInterval();
}
})
(2)play.json
代码如下:
{}
(3)play.wxml
代码如下:
<!--play.wxml-->
<view class="time">时间:{{time}}s</view>
<view class="score">分数:{{score}}</view>
<view class="play-box">
<block wx:for="{{blockData}}" wx:for-index="i" wx:key="i">
<view class="block-line" id="line-{{blockData[i].id}}">
<block wx:for="{{blockData[i].block}}" wx:key="*this" wx:for-index="j">
<view wx:if="{{blockData[i].block[j] == 0}}" id="block-{{blockData[i].id}}-{{j}}-{{0}}" class="block" bindtap="handleClick"></view>
<view wx:else class="block black" id="block-{{blockData[i].id}}-{{j}}-{{1}}" bindtap="handleClick"></view>
</block>
</view>
</block>
</view>
(4)play.wxss
代码如下:
/* pages/time/play.wxss */
.time {
position: fixed;
top: 2rem;
left: 50%;
font-size: 2rem;
color: #fc2736;
transform: translate(-50%, 0);
z-index: 10;
}
.score {
position: fixed;
top: 5rem;
left: 50%;
font-size: 2rem;
color: #fc2736;
transform: translate(-50%, 0);
z-index: 10;
}
.play-box {
width: 100%;
position: fixed;
left: 0;
bottom: 0;
}
.block-line {
width: 100%;
overflow: hidden;
}
.block {
width: 5rem;
height: 8rem;
float: left;
border: 1px solid #000000;
box-sizing: border-box;
}
.black {
background: #000000;
}
5. 急速模式配置(speed)
(1)play.js
代码如下:
// // play
var app = getApp()
Page({
data: {
typeName: '急速模式',
score: 0,
blockData:[],
scrollHeight: 0,
canRun: false
},
onLoad: function(){
var that = this;
// 设置title
wx.setNavigationBarTitle({
title: that.data.typeName
});
},
onReady: function(){
var array = [];
// 先生成一个10个长度的数组
for(var i = 0; i < 10; i++){
array.push(this.getNewLine(i));
}
this.setData({
blockData: array.reverse()
});
},
handleClickWhite: function(events){
// 点击白块一定会报错 差别在于报错文案
// 判断是否是点击的第一行
// 被点击的id
var id = events.currentTarget.id;
// 被点击的行
var line = id.split("-")[1];
// 数据
var blockData = this.data.blockData.concat().reverse();
// 当前分数
var score = this.data.score;
// 判断是否是第一行
if(line != this.getClickableBlockLine(blockData)){
this.handleWrong("请点击第一个黑块!游戏结束", score);
} else {
// 点击的第一行白块
this.handleWrong("别点白块!游戏结束", score);
}
},
handleClickBlack: function(events){
// 黑块是应该点击的块
// 判断是否是点击的第一行
// 被点击的id
var id = events.currentTarget.id;
// 被点击的行
var line = id.split("-")[1];
// 数据
var blockData = this.data.blockData.concat().reverse();
// 当前分数
var score = this.data.score;
// 可点击的第一行
var clickableLine = this.getClickableBlockLine(blockData);
// 判断是否是第一行
if(line == clickableLine){
// 点击了第一行黑块
// 判断是否是是第一次
if(score == 0){
// 启动滑动程序
this.run();
}
score++;
// 将黑块变灰块
this.getBlockBlackToGray(line, blockData);
// 分数++
this.setData({
score: score,
blockData: blockData.concat().reverse()
});
} else {
// 点击的不是第一行白块
this.handleWrong("请点击第一个黑块!游戏结束", score);
}
},
handleClickGray: function(events){
// 灰块是指黑块点击之后的块
// 其在显示是白块 并且同样不可点
var score = this.data.score;
this.handleWrong("别点白块!游戏结束", score);
},
run: function(){
// 滑动方法
var that = this;
var speed = 10;
this.setData({
canRun: true
});
var timer = setInterval(function(){
// 当前滑动距离
if(!that.data.canRun){
clearInterval(timer);
return;
}
var currentScrollHeight = that.data.scrollHeight;
// 当前分数
var score = that.data.score;
// 滑块数据
var blockData = that.data.blockData.concat().reverse();
if(Math.abs(currentScrollHeight) == 150){
// 滑到临界点
// 判断是否过期
// 判断条件是 第一个滑块的状态是否为已点击
if(that.checkFirstLineBlockClicked(blockData[0].block)){
// 没过期
// 继续 去除旧节点 插入新节点 scrolllHeight归0
var newId = blockData[blockData.length - 1].id + 1;
blockData.push(that.getNewLine(newId));
blockData.shift();
that.setData({
scrollHeight: 0,
blockData: blockData.concat().reverse()
});
return;
}
// 过期
// 报错
that.handleWrong("请点击白块!游戏结束", score);
return;
}
currentScrollHeight = currentScrollHeight - speed;
that.setData({
scrollHeight: currentScrollHeight
});
}, 20);
},
checkFirstLineBlockClicked: function(blockDataLine){
for(var i = 0; i < blockDataLine.length; i++){
if(blockDataLine[i] == 2){
return true;
}
}
return false;
},
getBlockBlackToGray: function(line, blockData){
for(var i = 0; i < blockData.length; i++){
if(blockData[i].id == line){
var currentArray = blockData[i].block;
for(var j = 0; j < currentArray.length; j++){
if(currentArray[j] == 1){
currentArray[j] = 2;
return;
}
}
}
}
},
getClickableBlockLine: function(blockData){
var line = 0;
for(var i = 0; i < blockData.length; i++){
var block = blockData[i].block;
for(var j = 0; j < block.length; j++){
// 行内四个元素 有1即可
if(block[j] == 1){
return blockData[i].id;
}
}
}
return line;
},
getNewLine: function(i){
// 生成一个标准的数据
var orderArray = [0,0,0,0];
// 生成一个随机数
var randomNum = Math.floor(Math.random() * 4);
// 赋值给对应的obj
orderArray[randomNum] = 1;
return {id: i, block: orderArray};
},
handleWrong: function(text, score){
this.setData({
canRun: false
});
wx.showToast({
title: text,
icon: 'cancel',
duration: 2000,
complete: function(){
// 将此分数存入全局变量
app.globalData.currentScore = score;
// 若此分数比最高分数还高 将其存入本地
if(score > app.globalData.speedScore){
app.globalData.speedScore = score;
wx.setStorageSync('speedScore',score);
}
var timer = setTimeout(function(){
wx.redirectTo({
url: '../end/end?type=speed&score=' + score
})
clearTimeout(timer);
}, 2000);
}
})
}
})
(2)play.json
代码如下:
{}
(3)play.wxml
代码如下:
<!--play.wxml-->
<view class="score">{{score}}</view>
<view class="play-box" style="bottom: {{scrollHeight}}px">
<block wx:for="{{blockData}}" wx:for-index="i" wx:key="i">
<view class="block-line" id="line-{{blockData[i].id}}">
<block wx:for="{{blockData[i].block}}" wx:key="*this" wx:for-index="j">
<view wx:if="{{blockData[i].block[j] == 0}}" id="block-{{blockData[i].id}}-{{j}}-{{0}}" class="block" bindtap="handleClickWhite"></view>
<view wx:elif="{{blockData[i].block[j] == 1}}" class="block black" id="block-{{blockData[i].id}}-{{j}}-{{1}}" bindtap="handleClickBlack"></view>
<view wx:else class="block gray" id="block-{{blockData[i].id}}-{{j}}-{{2}}" bindtap="handleClickGray"></view>
</block>
</view>
</block>
</view>
(4)play.wxss
代码如下:
/*page {
display: flex;
flex-direction: column-reverse;
}*/
.score {
position: fixed;
top: 2rem;
left: 50%;
font-size: 3rem;
color: #fc2736;
transform: translate(-50%, 0);
z-index: 10;
}
.play-box {
width: 100%;
position: fixed;
left: 0;
bottom: 0;
}
.block-line {
width: 100%;
overflow: hidden;
}
.block {
width: 5rem;
height: 8rem;
float: left;
border-left: 1px solid #000000;
border-right: 1px solid #000000;
box-sizing: border-box;
transition: .2s all;
}
.black {
background: #000000;
}
.gray {
background: #ffffff;
}
6. 游戏结束配置(end)
(1)end.js
代码如下:
var app = getApp()
Page({
data: {
currentScore: 0,
gameType: "",
heighestScore: 0,
backUrl: ""
},
onLoad: function(options){
var score = options.score;
var gameType = options.type;
var text = {endless: "无尽模式", time: "计时模式", speed: "极速模式"};
// 从全局变量中获取分数
this.setData({
currentScore: app.globalData.currentScore,
gameType: text[gameType],
heighestScore: app.globalData[gameType + "Score"],
backUrl: '../'+gameType+'/play'
});
}
})
(2)end.json
代码如下:
{}
(3)end.wxml
代码如下:
<view class="current">{{gameType}}</view>
<view class="current">本次分数:{{currentScore}}</view>
<view class="highest">最高分数:{{heighestScore}}</view>
<navigator url="{{backUrl}}" redirect class="go-again">再来一次</navigator>
<navigator url="../index/index" redirect class="go-home">返回首页</navigator>
(4)end.wxss
代码如下:
/* pages/end/end.wxss */
.current {
margin-top: 40rpx;
margin-bottom: 40rpx;
text-align: center;
font-size: 1.5rem;
}
.highest {
text-align: center;
font-size: 1.5rem;
}
.go-again {
text-align: center;
display: block;
width: 200rpx;
margin: 200rpx auto 0 auto;
padding: 20rpx;
border-radius: 10rpx;
color: #ffffff;
background: #fc2736;
}
.go-home {
text-align: center;
display: block;
width: 200rpx;
margin: 100rpx auto 0 auto;
padding: 20rpx;
border-radius: 10rpx;
color: #ffffff;
background: blue;
}
7. 日志文件配置(logs)
(1)logs.js
代码如下:
//logs.js
var util = require('../../utils/util.js')
Page({
data: {
logs: []
},
onLoad: function () {
this.setData({
logs: (wx.getStorageSync('logs') || []).map(function (log) {
return util.formatTime(new Date(log))
})
})
}
})
(2)logs.json
代码如下:
{
"navigationBarTitleText": "查看启动日志"
}
(3)logs.wxml
代码如下:
<!--logs.wxml-->
<view class="container log-list">
<block wx:for="{{logs}}" wx:for-item="log" wx:key="*this">
<text class="log-item">{{index + 1}}. {{log}}</text>
</block>
</view>
(4)logs.wxss
代码如下:
.log-list {
display: flex;
flex-direction: column;
padding: 40rpx;
}
.log-item {
margin: 10rpx;
}
三、别踩白块效果实现
1. 首页效果
2. 模块效果