使用Pixi.js实现《围追堵截》桌游(一)

前言

游戏体验地址:http://www.jiajialove.com/war-of-barriers/home

项目地址:https://github.com/jzllove9/war-of-barriers

本来只想简单画一下游戏的模块拆分。但当开始写这篇博客的时候才发现其实想用最简单的语言讲清楚还真不是一件容易的事情。比如以下这几个问题:

- 如何进行技术选型?
- 如何拆分游戏规则?
- 如何实现游戏交互?
- 如何实现回合机制?
- 如何限制用户操作?
- 如何提示用户操作?
- 其他...

问题很多,接下来就带着这些问题一点点来实现整个游戏逻辑。

技术选型

技术选型是个永恒的话题,在网上随便一搜就能搜出好多在各种 2D/3D 引擎相互之间的比对文章。本文之所以选择 pixi.js 的原因很简单:之前笔者用它做过项目,比较熟悉其API。

其实在目前的硬件设备环境下,无论使用什么样的2D引擎去实现这样一个小体量的游戏都绰绰有余。

一篇对各个引擎优缺点描述的文章:https://blog.logrocket.com/best-javascript-html5-game-engines/

准备工作

我们先需要先了解一些概念:

  1. 游戏规则: 《围追堵截》游戏规则

  2. A* 算法 A*是一种路径规划算法,它有较好的性能和准确度。在阅读本文时可以将其理解为一个黑盒的算法:输入一个二维数组 + 起始坐标 + 终点坐标,返回一个代表着路径的坐标数组,如下所示: A*

两篇介绍 A* 算法的文章: 英文 《Introduction to the A* Algorithm》 中文 《A星算法详解》

另外推荐一个 A* 的 js 实现库,本文中的游戏就使用了该库: https://github.com/prettymuchbryce/easystarjs

游戏绘制对象分析与实现

首先,在正式开发前,先分析下《围追堵截》这款桌游本身。整个桌游由以下部分组成:

棋子 * 2(也有 4 个棋子的模式,本文不考虑该情况)
棋盘 * 1
阻挡墙 * 20

在项目中,我们针对这几个组成部分,创建出对应的绘制对象类:

棋子绘制类: Role
棋盘绘制类: Board
阻挡墙绘制类: Block

其次,我们还需要一个整体的类来管理游戏对象的初始化,回合切换,对界面暴露事件和方法等,所以再创建一个游戏类:

游戏类: Game

另外,我们知道围追堵截这个桌游是一个 9 * 9 的棋盘,但其实在游戏过程中不仅格子本身可以交互(移动角色),格子之间的缝隙也是可以交互的(放挡板),这就说明缝隙在棋盘中也是一个可交互对象(即绘制对象)。所以我们需要为他们分别创建绘制类,并取名为 Rect 和 Gap。

棋盘格绘制类: Rect
缝隙绘制类: Gap

需要注意的是,由于缝隙作为绘制对象的加入,每行每列变为 9 个格子 + 8 个缝隙,原本的 9 * 9 棋盘的实际数量为 17 * 17:

const N = 9
const bordRow = 2 * N - 1 // 17
const bordCol = 2 * N - 1 // 17

最后,我们分别来实现这些类的初始化操作:

需要注意的是,在初始化时我们需要根据 Gap 不同的类型来进行不同的绘制操作。

可以看到,棋盘的绘制对象分部符合以下规律:

 - 偶数行:
    - 偶数列:棋盘格子
    - 奇数列:缝隙(纵向)
 - 奇数行:
    - 偶数列:缝隙(横向)
    - 奇数列:缝隙(无方向)

根据以上规律,在循环中分别生成不同的格子,缝隙绘制对象的实例,就可以完成棋盘的初始化。

棋子绘制实例 * 2 -> role1,role2
棋盘绘制实例 * 1 -> board(由多个 rect 实例 + 多个 gap 实例组成)
阻挡墙绘制实例 * 1 -> block (一个绘制实例就可以绘制所有后续游戏逻辑中产生的阻挡墙,所以这里仅需要一个实例即可)

经过上面几个步骤后,我们已经基本梳理清晰了游戏需要用到的所有绘制类,并完成了它们的初始化操作。

在后面的文章中,我们将从绘制对象的管理,游戏规则的分析,联动界面等角度入手继续完善整个游戏逻辑,包括创建用来管理绘制对象的虚拟对象,以及利用这些虚拟对象进行路径计算,实现游戏规则等。除此之外,还会根据游戏需要继续丰富之前已经实现的绘制类。