【飞行棋】多人游戏-微信小程序开发流程详解

可曾记得小时候玩过的飞行棋游戏,是90后的都有玩过吧,现在重温一下,这是一个可以二到四个人参与的游戏,通过投骰子走棋,一开始靠运气,后面还靠自己选择,谁抢占先机才能赢,还可以和小伙伴们一起玩,狭路相逢勇者胜,可谓趣味多多。

创建小程序

打开电脑上的微信开发工具,如下图所示,新建一个小程序项目
新建微信小程序

例如,项目名称为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组件的节点和绘制API
      this.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个,在开始页面上是可以选择人数的

请添加图片描述

三缺一,没事儿,让我的小伙伴们能一起玩就对了

关于项目

如果要看项目源码 请点这里看,在资源一栏下可以找到飞行棋的源码,放心下载,感谢支持!

如果是在手机上看会有可能找不到资源一栏,就在电脑浏览器上看,

喜欢的话,点个赞收藏吧,遇到什么不明白的地方请主动留言,作者看到会回复。

猜你喜欢

转载自blog.csdn.net/zs1028/article/details/130549526