适合新手练习的前端React小游戏(大佬勿骂)
声明:作者本身只是个刚工作半年的前端小白,所以有关算法优化方面较差,希望各位大佬勿喷。
简介
设计思路借鉴于此作者:https://juejin.cn/post/7147245442172977189 (有兴趣的可以看看)
此项目基于 ts、umi、react、less 等技术完成(需要了解 react ),并采用纯前端渲染未采用任何后台服务。本文只注重设计思路哦。
仓库地址
项目预览
还在弄,勉强看看动图吧。
设计思路
由于我习惯于对需求进行分步处理的,所以将此游戏分成以下步骤。
- 搭建大体场景 (背景图,存放栏,工具栏)
- 实现格子的随机静态渲染
- 点击格子后的逻辑
- 工具栏三个工具逻辑
搭建大体场景 (背景图,存放栏,工具栏)
这里的简单用下HTML、CSS就能使用,就不过多讨论了。
处理格子的随机渲染
格子的随机渲染可以分成以下步骤。
- 实现单个格子的静态渲染
- 实现多个格子的动态渲染
- 实现格子的随机渲染
实现单个格子的静态渲染
- 每个格子我们需要一个正方形。
<div className={styles.grid}></div>
@grid-size = 20px; ; //格子大小,不支持less的话,直接width/height直接赋值也可以的
.grid {
box-sizing: border-box;
display: inline-block;
width: @grid-size;
height: @grid-size;
border: 1px solid #000;
}
有人会问你这格子咋没有设置背景图片啊,太丑了也?
别急,我们一步步来。到此,我们单个格子就做完了,虽然很丑但是有效果就行。
实现多个格子的静态渲染
- 因为我们要指定每个格子的位置,所以得用 定位(position) 来做。
- 例如下图,怎么实现下图的效果呢?
1号格子:第一行第一列 1-1
2号格子:第一行第二列 1-2
3号格子:第一行第二列 2-1
不难发现:通过定位就可以发现:
left =(列数 - 1) * 格子大小;
top = (行数 - 1) * 格子大小;
4. 那么我们就简单了,给每个格子设置一个对象属性然后渲染就行。
{
col:0,//列数
row:0,//行数
left:0,//左侧偏移量
right:0//右侧偏移量
}
逻辑如下:
element.left = (col - 1) * GridSize;
element.top = (row - 1) * GridSize;
至此,我们就完成了简单的一层格子的渲染。
实现多层格子的动态渲染
- 层级很简单,想到用 z-index 来做,那么在格子对象内添加一个 zIndex 属性。
- 格子对象再添加一个 state 属性,用于判断格子是否能点击 ( 0:可点击 , 1:不可点击 )。
{
zIndex: 0; // 图层(新增)
col:0,//列数
row:0,//行数
left:0,//左侧偏移量
right:0,//右侧偏移量
state: 0 //0:可点击 1:不可点击
}
如何实现每层格子的偏移呢?一样,先找规律。
从上图图中不难发现。
4号格子相比于1号格子往下和往右都偏移了半个格子的长度。
5号格子相比于2号格子往下和往右都偏移了半个格子的长度。
| 格子号 | 层级 | 行数 | 列数 | 索引(层级-行数-列数) |
|---|---|---|---|---|
| 4 | 2 | 1 | 1 | 2-1-1 |
| 1 | 1 | 1 | 1 | 1-1-1 |
| 5 | 2 | 1 | 2 | 2-1-2 |
| 2 | 1 | 1 | 2 | 1-1-2 |
利用规律,得出以下规律。
element.left = zIndex * 0.5 * GridSize + (col - 1) * GridSize;
element.top = zIndex * 0.5 * GridSize + (row - 1) * GridSize;
这样就能完成以下效果了。
然后我们要去判断格子是否能被点击?
| 格子号 | 层级 | 行数 | 列数 | 索引(层级-行数-列数) |
|---|---|---|---|---|
| 4 | 2 | 1 | 1 | 2-1-1 |
| 1 | 1 | 1 | 1 | 1-1-1 |
| 2 | 1 | 1 | 2 | 1-1-2 |
| 3 | 1 | 2 | 1 | 1-2-1 |
利用规律,得出以下规律。
目标格子的层级 < 别的格子的层级 && 两个格子的偏移量相差 < 0.5 * GridSize。
那么格子就不能被点击了。
问题来了,如何有效的循环所有格子对象的进行是否点击判断?
因为我这只用了单层数组进行简单处理,所以每次判断都得循环所有数组。后续可能得采用树图进行优化。
总结,因为我菜。
//初始化格子状态
function initGirdState(GridList: GridNode[], GameSetting: GameSetting) {
for (let i = 0; i < GridList.length; i++) {
let {
left: targetLeft,
top: targetTop,
zIndex: targetZIndex,
state: targetState,
} = GridList[i];
if (targetState !== 2) {
let isClick = true;
//TODO 需要优化
for (let j = 0; j < GridList.length; j++) {
let {
left: OtherLeft,
top: OtherTop,
zIndex: OtherZIndex,
state: OtherState,
} = GridList[j];
if (
targetZIndex < OtherZIndex &&
Math.abs(OtherLeft - targetLeft) <= GameSetting.GridSize * 0.5 &&
Math.abs(OtherTop - targetTop) <= GameSetting.GridSize * 0.5 &&
OtherState !== 2
) {
isClick = false;
continue;
}
}
GridList[i].state = isClick ? 1 : 0;
}
}
}
实现多层格子的随机渲染
这里每个人有自己的生成想法,只要我们生成的格子的 层级、行数、列数 三者都不重复就行了。
以下是我的烂代码(勿骂)。
- 为了保证格子的唯一性:对象内添加id(层级-行数-列数)属性。
- 为了添加背景图片:对象内添加type属性,然后require(
@/assets/imgs/${type}.png)。
我知道肯定会有人会说你这么多图片,咋不用雪碧图啊?别问,问就是懒得弄(开玩笑的,主要是后面有支持自己上传图片生成的,觉得雪碧图比较麻烦hhhh,还是懒)
{
id: 0; // 保证图片的唯一值
type:0; //背景图片
zIndex: 0; // 图层
col:0,//列数
row:0,//行数
left:0,//左侧偏移量
right:0,//右侧偏移量
state: 0 //0:可点击 1:不可点击
}
格子大概样式
<div
className={styles.grid}
style={{
position: gridInfo.state === 2 ? "static" : "absolute",
top: gridInfo.state === 2 ? undefined : gridInfo.top,
left: gridInfo.state === 2 ? undefined : gridInfo.left,
zIndex: gridInfo.zIndex,
cursor: gridInfo.state === 0 ? "default" : "pointer",
backgroundImage: `url(${
imgFiles.length > 0
? imgFiles[gridInfo.type]
: require(`@/assets/imgs/${gridInfo.type}.png`)
})`,
backgroundColor: gridInfo.state === 0 ? "#888" : "#fff",
}}
onClick={handleClick}
></div>
//随机生成格子列表
function initGridList(GameSetting: GameSetting) {
const { Sort, Layers, Row, Col } = GameSetting;
let GridList: GridNode[] = []; //总渲染格子列表
let flagList: flagListProps = {}; //总渲染格子标签列表-判断是否重复
//初始化格子样式
const initGirdStyle = () => {
let zIndex = Math.floor(Math.random() * Layers); //随机生成层数
return {
zIndex,
row: Math.ceil(Math.random() * (Row - zIndex)), //根据层数,随机生成行
col: Math.ceil(Math.random() * (Col - zIndex)), //根据层数,随机生成列
};
};
//生成3*sort个格子
for (let i = 0; i < Sort; i++) {
let type = Math.floor(Math.random() * 12); //获取当前渲染图片值 1-12
let newGirds: GridNode[] = [];
//根据type初始化生成三个格子
for (let j = 0; j < 3; j++) {
let newGird = {
id: "",
state: 0,
left: 0,
top: 0,
type,
...initGirdStyle(),
};
//判断三个格子是否有重复位置的
createUniqueGrid(flagList, newGird, GameSetting);
newGirds.push(newGird);
}
//推送新数组到GridList
GridList.push(...newGirds);
}
//初始化所有格子状态
initGirdState(GridList, GameSetting);
return GridList;
}
//生成唯一格子
function createUniqueGrid(
flagList: flagListProps,
element: GridNode,
GameSetting: GameSetting
) {
const { GridSize, Layers, Row, Col } = GameSetting;
let { zIndex, row, col } = element;
if (
flagList[zIndex] &&
flagList[zIndex].hasOwnProperty(`${row}-${col}`) &&
flagList[zIndex][`${row}-${col}`] === 1
) {
element.zIndex = Math.floor(Math.random() * Layers);
element.row = Math.ceil(Math.random() * (Row - element.zIndex));
element.col = Math.ceil(Math.random() * (Col - element.zIndex));
createUniqueGrid(flagList, element, GameSetting);
} else {
if (!flagList[zIndex]) {
flagList[zIndex] = {};
}
flagList[zIndex][`${row}-${col}`] = 1;
element.id = `${zIndex}-${row}-${col}`;
element.left = zIndex * 0.5 * GridSize + (col - 1) * GridSize;
element.top = zIndex * 0.5 * GridSize + (row - 1) * GridSize;
}
}
至此,我们完成了多层格子的随机渲染。
后续的点击事件其实比较简单了( 主要是累了,不想写了 )。
还有自定义设置的页面,可以设置游戏难度、最大行数、最大列数、最大层级什么的后续再分享( 因为我觉得这篇文章没有人看 )。
这里是普通又想做不普通的前端打工人。大家新年快乐哦!!!!!