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

news2024/11/24 17:43:32

可曾记得小时候玩过的飞行棋游戏,是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组件的节点和绘制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个,在开始页面上是可以选择人数的

请添加图片描述

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

关于项目

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 三大特殊数据类型常见命令

Geospatial 朋友的定位&#xff0c;附近的人&#xff0c;打车距离计算 底层是 Zset&#xff0c;即可以使用Zset的命令操作Geospatial Redis3.2 开始支持的 1.添加地理位置 两极无法添加经度&#xff1a;-180 ~ 180&#xff08;度&#xff09;纬度&#xff1a;-85.05112878 ~ 8…

一、RestTemplate的使用

目录 1、新建项目springcloud&#xff08;File--->New--->Project&#xff09; 使用maven quickstart快速新建Maven项目 输入项目名称和Maven信息 确认Maven目录、配置文件、本地仓库&#xff0c;Finish即可 2、创建聚合项目springcloud-member、springcloud-order(项…

【人力资源管理】第2集 免费开源ERP: Odoo 16 Recruitment招聘管理 构建一体化企业人力资源管理

文章目录 前言一、概览二、主要功能1.组织空缺职位和职位申请2.追踪工作机会&#xff08;查看哪个渠道收到的申请最多&#xff09;3.定制您的招聘流程4.集成文档&#xff08;定义您自己的文件管理流程&#xff09;5.与Odoo应用程序完全集成 总结 前言 轻松处理您的招聘流程。 …

C++ 中到底是应该include .h文件还是应该include .cpp文件

在阅读一个较大的解决方案中&#xff0c;对于其他文件夹下的.h和.cpp文件&#xff0c;有时候#include“XXX.h”文件&#xff0c;有时候是#include“XXX.cpp”文件&#xff0c;而且二者还不能更换。下面就好好分析一下他们二者的区别。 测试 测试&#xff1a;XXX.h和XXX.cpp…

连接器:一种可靠耐用、节约成本的同为科技(TOWE)工业连接器

随着我国经济建设水平的飞速发展&#xff0c;工业连接器被广泛应用于工业、化工、机场、船舶、码头、建筑、铁路、医疗、会展、商业演出等领域。工业连接器的作用是用于连接一个电路导体与另一个电路导体、或一个传输元件与另一个传输元件的装置&#xff0c;并且为两个电路子系…

知识变现海哥:六种常见的知识变现渠道

什么是知识变现&#xff1f;就是用你所会的知识技能&#xff0c;在网上进行展示&#xff0c;吸引人们为其付钱。互联网发展到今天&#xff0c;我们可以看到它各方面已经逐渐完善了&#xff0c;但曾经在互联网上的不花钱的知识&#xff0c;在今天却要为其花费金钱。在此基础上&a…

CCSA TC1演讲分享 | 全域智能,构建平台化生态

日前&#xff0c;中国通信标准化协会&#xff08;CCSA&#xff09;成功召开互联网与应用技术工作委员会&#xff08;TC1&#xff09;第四十次全会&#xff0c;全会期间&#xff0c;TC1 WG7 IT内控与审计技术标准工作组举办了第3次工作组会议。此次会议重点讨论了各项标准文稿及…

微服架构基础设施环境平台搭建 -(四)在Kubernetes集群基础上搭建Kubesphere平台

微服架构基础设施环境平台搭建 -&#xff08;四&#xff09;在Kubernetes集群基础上搭建Kubesphere平台 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动…

Docker安装及容器安装

Docker安装及容器安装 一、Docker简单介绍 1、Docker是什么 Docker是基于Go语言实现的云开源项目。 Docker是一个开源的应用容器引擎&#xff0c;是容器技术的一种&#xff0c;采用Go编程语言编写。虽然 Docker把容器技术推向了巅峰&#xff0c;但其实&#xff0c;还有其他容…

蓝牙智能升降桌解决方案介绍

传统桌子在办公和学习中具有很大的普及度&#xff0c;但是长时间久坐却会对人体造成可怕的危害。由于不合理的坐姿、长时间久坐等习惯&#xff0c;不仅影响血液循环,让人体感到不适,还会出现视力、颈椎、腰椎、心脏等一系列健康问题。此外&#xff0c;传统桌子通常是固定高度且…

【C++进阶2--多态】面向对象三大特性之一,多种形态像魔法?

今天&#xff0c;带来C多态的讲解。 多态和继承并用&#xff0c;能产生“魔法般的效果”。 *文中不足错漏之处望请斧正&#xff01; 见见多态 是什么 使得父类指针或引用有多种形态。 怎么使它有多种形态呢&#xff1f;咱们先见见猪跑。 见见猪跑 class Base { public:v…

第05章_排序与分页

第05章_排序与分页 1. 排序数据 1.1 排序规则 使用 ORDER BY 子句排序 ASC&#xff08;ascend&#xff09;: 升序DESC&#xff08;descend&#xff09;:降序 ORDER BY 子句在SELECT语句的结尾。 1.2 单列排序 SELECT last_name, job_id, department_id, hire_date FROM …

Java基础学习(15)

Java基础学习 一、IO流进阶1.1 缓冲流1.1.1字节缓冲流1.1.2 字符缓冲流 1.2 转换流1.3 序列流1.4 反序列化流 /对象操作输入流1.4.1 序列化流/反序列化流的细节汇总 1.5 打印流1.5.1 字节打印流1.5.2 字符打印流 1.6 解压流、压缩流1.7 Commons-io1.8 hutool工具包 一、IO流进阶…

【C#】RemoveAt索引越界问题

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

使用FFMPEG和SDL2实现音视频同步的简易视频播放器

程序框架 由于之前都是针对FFMPEG某一个功能做的测试和学习。这里我简单做了一个视频播放器&#xff0c;并简单做了音视频同步。在此记录大致过程。 大致框架如下&#xff1a; 主线程 1.加载视频文件&#xff0c;查找音视频流信息 2.初始化音视频解码器 3.初始化SDL并设置…

题解校验码—CRC循环校验码与海明校验码

码距 一个编码系统的码距是任意两个码字的最小距离。 例如个编码系统采用三位长度的二进制编码&#xff0c;若该系统有四种编码分别为&#xff1a;000&#xff0c;011&#xff0c;100&#xff0c;111&#xff0c;此编码系统中000与111的码距为3&#xff1b;011与000的码距为2…

POE:性价比最高的 AI 整合网站

创作不易&#xff0c;如果本文对你有帮助&#xff0c;胖友记得一键三连 &#x1f62d;。更多 AI 优质内容推荐请关注主页 “AI” 专栏&#xff0c;笔者会不定期更新觉得自己用下来还不错的 AI 相关产品。 1.介绍 Poe 是一款同时整合了 ChatGPT、Sage、GPT-4、Claude、Claude-in…

经典神经网络(2)AlexNet及其在Fashion-MNIST数据集上的应用

2、深度卷积神经网络AlexNet ImageNet 数据集&#xff1a;一个开源的图片数据集&#xff0c;包含超过 1400万张图片和图片对应的标签&#xff0c;包含2万多个类别。 自从2010 年以来&#xff0c;ImageNet 每年举办一次比赛&#xff0c;即&#xff1a;ImageNet 大规模视觉识别挑…

数组排序算法

数组排序算法 一、冒泡排序算法二、直接选择排序三、插入排序四、反转排序 一、冒泡排序算法 冒泡排序算法&#xff1a; 类似气泡上涌的动作&#xff0c;会将数据在数组中从小到大或者从大到小不断向前移动。 基本思想&#xff1a; 冒泡排序的基本思想是对比相邻的两个元素值&…

并发编程(二) — 内存可见性问题

目录 前言 内存可见性问题 synchronized volatile CAS算法 CAS算法原理 CAS算法应用场景 CAS算法代码实现 参考目录 前言 在谈共享变量的内存可见性问题之前&#xff0c;先谈谈线程安全问题 &#xff0c;线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同…