可曾记得小时候玩过的飞行棋游戏,是90后的都有玩过吧,现在重温一下,这是一个可以二到四个人参与的游戏,通过投骰子走棋,一开始靠运气,后面还靠自己选择,谁抢占先机才能赢,还可以和小伙伴们一起玩,狭路相逢勇者胜,可谓趣味多多。
文章目录
- 创建小程序
- 开始页面
- 游戏页面
- 游戏逻辑
- 画棋盘
- 画棋子
- 投骰子
- 游戏规则
- 1. 投骰子
- 2. 移动棋子
- 3. 选择棋子
- 测试游戏
- 关于项目
创建小程序
打开电脑上的微信开发工具,如下图所示,新建一个小程序项目
例如,项目名称为
flying-chess
,依次选择
- 小程序
- 不使用云服务
- 使用JavaScript - 基础模板
开始页面
新建的项目中,系统有自动创建的第一个页面,文件在/pages/index/index.wxml
,就在这里添加布局,
考虑到游戏可视情况选择几人玩,要在布局里添加表单组件,让用户选择,
然后点击开始游戏即可,显示结果如下图所示
在对应的
index.js
逻辑文件里,添加开始游戏按钮点击事件,
在事件方法里写上代码调用系统的API如下,可打开游戏页面:
wx.navigateTo({ url:"/pages/game/game", ... })
还需要把用户选择的数据传过去;
游戏页面
接下来,新建一个文件夹game
,建一个游戏页面,文件名都是game
,
在文件/pages/game/game.wxml
中,添加游戏页面的布局,代码如下
<view class="game-panel"><image class="canvas" src="{{bgImg}}" /><view class="float-panel id{{index}} {{index==current ? 'active' : ''}}" wx:for="{{toasts}}" wx:key="index"><view class="title"><text>{{item}}</text></view></view><canvas class="canvas" id="canv" type="2d" disable-scroll="true" bindtouchstart="onTouchStart"></canvas>
</view>
<!-- 这里显示游戏底部的布局 --></view>
</view>
游戏页面上只用了一个
canvas
画布组件,一个背景图片,一个float-panel
类的视图层显示数据,都是层层叠放显示的
游戏逻辑
就在game.js文件里写游戏逻辑,当页面加载渲染完成时,系统会调用其中的方法onReady()
,就在这里开始写,先获取canvas组件的数据,
画棋盘
实现画棋盘的逻辑并不复杂的,可以这么做,先用canvas绘制好网格,然后在对应的格子上绘制各种图案就可以,
就像平时用电脑办公的表格制作软件Excel
,单元格可以合并的,如下图所示,
画棋盘的代码放在项目里的一个
/utils/game-map.js
文件中,作为模块来用
需要用模块的时候,用import
导入一下这个模块文件,看如下代码
import GameMap from '../../utils/game-map.js';Page({/*** 页面的初始数据*/data: {bgImg: '',//显示背景图片isAnimaging: false,//是否在动画中isNewAir: false,//是否再加棋子(派新飞机)toasts: ['x4', 'x4', 'x4', 'x4'],//飞机场显示的信息current: 0,//允许哪个玩家操作,默认第一个玩家(1号)currentColor: 'none',//设置游戏地图底部的控件背景颜色isSelectMode: false,//如果一个玩家派出多个棋子,会进入选择棋子状态isEndGame: false,//是否结束游戏},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {//获取canvas组件的数据wx.createSelectorQuery().select('#canv').fields({size: true,node: true}, res => {//...省略了//这里设置canvas组件的节点和绘制APIthis.canvasData = {canvas: res.node,context: res.node.getContext('2d')};//获取底部控件对象 组件 将用来操作this.ctrlPanel = this.selectComponent('#ctrl-panel');//初始化棋盘this.initChessPanel();//...省略了}).exec()},
从上面代码看出来了,调用方法initChessPanel()
就可以绘制游戏地图,来看看怎么实现的呢
//设置初始游戏数据
const initGameData = {bgColor: '#55A3FF',//棋盘背景色preCurrentUid: -1,//上一个玩家id,-1表示没有
};
//用不同的四个颜色分别表示四个玩家的棋子
const initUserList = ['#E60116', '#FFC700', '#0277A8', '#08983F'].map((color, i) => {return {chessColor: color,//棋子颜色isJoin: this.joinUsers.indexOf(i) >= 0,//是否加入};
});
//这里调用模块文件game-map.js的GameMap地图对象,把上面的配置数据传入即可
const map = new GameMap(this.canvasData, initUserList, initGameData);
//调用地图对象的绘制地图上所有格子的方法,格子就是棋子的位置,生成图片数据
let bgImg = map.drawMapGrids();
let current = this.data.current;
//设置好地图数据,将来用到
this.gameData = map.gameData;
//更新显示到页面
this.setData({bgImg,//把地图显示到背景中currentColor: this.gameData.userlist[current].chessColor,gameToast: `请${current + 1}号玩家点击投骰子`
});
//省略了...
棋盘上各种颜色都是可以替换的,例如到了晚上,背景就显示漆黑的夜空;
其中GameMap
是一个游戏地图对象,在模块文件中有实现绘制地图,能给出坐标,
想想生活中用到的地图导航,作用一样的,
其中的joinUsers
是从开始页面传来的数据,表示有哪些玩家加入游戏
把画好的棋盘生成一个图片数据,设置到背景图片组件中显示,
画出来后,效果如下图
画棋子
地图有了,再把所有棋子画出来,
开始玩的时候,棋子都是放在飞机场上的,每个玩家都有四个棋子,
在调用方法initChessPanel()
里面继续写,代码如下,
//给地图设置棋子上的图像,就是飞机图片
map.setChessImg('/static/fly.png',()=>{//设置好,就可以绘制棋子了this.redraw();
});
然后调用方法redraw()
去绘制所有棋子,代码如下
const { canvas, context: ctx } = this.canvasData;//canvas组件的数据
const { grids, userlist, size, chessImg } = this.gameData;//游戏的数据
const r = size / 2;//棋子半径
const { toasts } = this.data;//显示游戏状态的数据
//因为这是重绘方法 使用前先清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
//把参与的玩家的棋子全部画出来
userlist.forEach((user, uid) => {let chesses = [];//记录可以绘制的棋子user.chesses.forEach((chess, i) => {//这里判断一下棋子的状态,如没有隐藏,且棋子有指向棋盘上的坐标,就可以加到记录中if (chess.isHide!=true && chess.gridIndex >= 0) {//...省略了继续判断逻辑}});//绘制棋子的chesses.forEach((chess, i) => {let g = grids[chess.gridIndex];//...省略了//绘制棋子的方法this.drawChessAtLocation(user, g);//判断棋子的数量,这是在同一个坐标上被叠放的棋子if (chess.count > 1) {let p = {//...};//给棋子画上数量标记ctx.fillText('' + chess.count, p.left, p.top);}});//判断这个玩家是否加入游戏if (user.isJoin) {//绘制玩家飞机场上的棋子user.chesses.forEach(chess => {if (chess.gridIndex < 0) {ctx.drawImage(chessImg, chess.left - r, chess.top - r, size, size);}});//更新玩家的信息toasts[uid] = `${uid + 1}号玩家`;} else {toasts[uid] = ``;}
});
//更新显示
this.setData({toasts
})
其中用到了方法
drawChessAtLocation(user, g)
,这是实现在地图上画棋子的,只传入参数用户数据和格子坐标
把所有棋子画出来后, 效果图如下
投骰子
由于绘制投骰子动画会变得复杂,需要写很多代码,不如做一下布局,这样好弄点,省了不少代码吧,
就用控件组件来代替投骰子,控件组件放在棋盘的底部,在文件/pages/game/game.wxml
中加上如下布局代码
<view><block wx:if="{{isEndGame}}"><text>{{gameToast}}</text></block><block wx:else><ctrl-panel id="ctrl-panel" isGameEnd="{{isGameEnd}}" isDisabled="{{isAnimaging || isSelectMode}}" bindstart="onClickStart" bgColor="{{currentColor}}"><text>{{gameToast}}</text></ctrl-panel></block><view>
这个组件ctrl-panel
是放在别处的,也是自定义组件,需要的时候可以拿来直接用,用这个来选择操作再好不过了,
看看控件在游戏页面的布局显示效果,如下图
这控件不仅仅是用来投骰子哦,还有一些细节待发现
游戏规则
接下来,写到游戏规则了,实现过程会复杂一些,
再理清一下思路,想想应该怎么做才合适呢,
飞行棋的游戏规则大致是这样的,没有玩过的可以了解一下:
- 棋子是顺时针方向走的,目的是走到自己颜色对应的飞机升降点;
- 若投骰子投到6点,可以选择再拿一个棋子(派一个新飞机),然后再投骰子一次,让新飞机走;
- 若走到自己对应颜色的一格,可以选择再走4步;
- 若走到有连接线的一格,可以选择直接飞行,到对面的相同颜色一格;
- 选择直接飞行时,如果中间的格子有遇到对手的棋子,就打回飞机场;
- 棋子走到一格子上有自己的棋子,是可以叠放的,
- 若是走到对手的棋子上就打回,如对手有叠放棋子,自己和对手的棋子就全部打回;
- 棋子走到升降点中间最接近的一格子上就算游戏胜利;
按照飞行棋的游戏规则,实现游戏逻辑,觉得难不难呢,可以分三步实现,能把难度降低,
1. 投骰子
要移动棋子前,先投骰子,这部分靠运气,通过概率分配实现,
投骰子的逻辑在组件ctrl-panel
中有实现,代码如下
// 取1~6之间的随机数
let num = Math.floor(Math.random()*6)+1;
可见
Math
的一些函数有经常用到
投完骰子后,组件ctrl-panel
会传来事件,调用一个方法onClickStart(e)
,
是写到game.js
里面的,代码如下,
const { stepNum } = e.detail;
//这里记录一下步数
this.stepNum = stepNum;
//记录玩家在棋盘中的所有棋子id
let indexes = [];
//...省略了
//定义走下一步的方法
let next = () => {//判断有没有派过新飞机,以及棋子有2个以上,让玩家先选择棋子再走if (this.isNewAir != true && indexes.length > 1) {this.setData({isSelectMode: true,//设置选择棋子状态gameToast: `请${current + 1}号玩家选择棋子`});return;}//开始动画 看出棋子移动效果this.startAnimation(false, () => {//动画结束会执行到这里,判断一下是否派过新飞机了,重置一下状态if (this.isNewAir) this.isNewAir = false;});
};
//获取步数后,判断是否是6点,是的话,让用户选择是否再拿一个棋子(派新飞机)
if (stepNum == 6 && this.isNewAir != true && indexes.length > 0) {if (user.chessIndex >= 0) {//...省略了}//下一步next();return;
}
//没有就继续,下一步
next();
2. 移动棋子
获取步数后,就可以移动棋子了,调用的方法startAnimation(used, callback)
,
需要通过动画实现棋子一步一步走的效果,代码如下
const { current } = this.data;//表示当前的玩家id
const { grids, size, userlist } = this.gameData;//游戏数据
this.gameData.preCurrentUid = current;//记录到上一个
//省略了...
//获取步数
let num = this.stepNum;
//动画结束方法
let end = (index) => {//走完棋子,动画也就结束了,调用此方法处理一下this.endAnimation(chess, index, used, callback);
};
//下一步移动方法
let next = (i) => {//省略了...//棋子位置变了,重新画个棋子this.drawChess(user, d);//如果步数没走完,继续调用下一步方法setTimeout(() => next(i + 1), StepTimeMs);
};
//获取棋子在地图中指定位置上的格子数据
let d = grids[chess.gridIndex];
//判断是否到了自己开始升降的位置,nodes是升降点的地图数据
if (d.nodes && d.nodes[0].grid.cid == current) {//省略了...this.takeGridChess(user, () => {this.enterNextNode(user, chess, d.nodes, num, chess.nodeIndex);});return;
};
//调用带走的棋子方法
this.takeGridChess(user, () => next(1));
调用移动棋子的方法参数used, callback分别是是否使用过,动画结束时会回调,
其中方法takeGridChess()
就是把正在移动的棋子拿走,然后重新绘制整个棋子,实现动画效果,
方法enterNextNode()
就是在进入升降航道路线时才调用的
还有,动画结束需要处理的方法是endAnimation(chessi, index, used, callback)
,
这实现了棋子到目标格子上做出反应处理的逻辑,代码如下
const { current } = this.data;
const { userlist, grids, size } = this.gameData;
const user = userlist[current];
const chess = user.chesses[user.chessIndex];
const gridIndex = chess.gridIndex;
let end = () => {//...省略了//处理结束,更新一下显示数据this.setData({isAnimaging: false,gameToast: `请${uid + 1}号玩家点击投骰子`});//重新绘制所有棋子this.redraw();//需要结束回调时 就回调if (callback instanceof Function) callback();
};
switch (gridIndex) {case 6:case 19://...{//执行到这里,说明是走到可以直线飞行的格子上,调用控件组件让玩家选择要不要直线飞行this.ctrlPanel.showSelectAirAtLineModal(res => {if (!res.confirm) {//没选择直线飞,就继续判断this.isBeatBackChess(gridIndex,current);end();return;}let i = 0;//直线飞行动画const next = () => {//...省略了i++;if(i>10) {//...省略了this.isBeatBackChess(chess.gridIndex, current, gridIndex + 6);end();return;}//...省略了setTimeout(next, 100);//0.1s移动一次}; this.takeGridChess(user, next);});return;}default:{//...省略了//调用判断是否碰到对方的棋子方法this.isBeatBackChess(chess.gridIndex, current);}
}
end();
3. 选择棋子
只有当玩家的飞机场里至少有两个都出发了,才是可以选择棋子的,
在game.js
里写,是canvas组件绑定是触摸事件方法onTouchStart(e)
,选择棋子的代码如下
if (this.data.isEndGame) return;//游戏结束时,不处理操作
const touch = e.touches[0];
const { userlist, grids, size } = this.gameData;
const { current, isSelectMode } = this.data;
// 获取正在操作的玩家数据
let user = userlist[current];
let chessIndex = user.chesses.findIndex((c, i) => {//...省略了
});
//判断是否选择到棋子
if (chessIndex >= 0) {//更新玩家选择的棋子user.chessIndex = chessIndex;//判断是否是选择状态,若是的话,就开始走棋动画if (isSelectMode) {this.startAnimation();}
}
测试游戏
就讲到这里,篇幅有限,上面都有讲了重点的,还有几个方法就不讲了,
可以看看项目源码,直接运行,看到有感觉,里面代码并不多,可以参考学习一下,
最后看一下飞行棋小程序的运行效果图,怎么样,可以吧
若想一起玩的人数不够4个,在开始页面上是可以选择人数的
关于项目
如果要看项目源码 请点这里看,在资源一栏下可以找到飞行棋的源码,放心下载,感谢支持!
如果是在手机上看会有可能找不到资源一栏,就在电脑浏览器上看,
喜欢的话,点个赞收藏吧,遇到什么不明白的地方请主动留言,作者看到会回复。