给羊了个羊做一个地图编辑器,顺便实现了个状态库
在上一篇文章中我们实现了一个基本的羊了个羊游戏,这次我们在原来的基础上增加地图编辑器的功能 juejin.cn/post/714572…
先上例子🌰
效果图如上
需求分析
- 地图编辑器(上图左侧):
-
- UI:一个网格矩阵,每个勾选的格子(红色)表示卡牌放置的位置
- 保存地图: 保存当前数据到本地,并且触发游戏区渲染
- 清除缓存:清除已编辑的地图内容
- 游戏区:
-
-
🐑了个羊基本玩法
-
根据地图编辑器的数据渲染出地图
-
游戏是三张牌触发消除,所以地图也画3层(3的倍数即可)
-
实现原理
地图编辑器
-
数据模型
首先定义网格的数据模型,这里使用一个二位数组来表示网格,0表示空,1表示勾选
interface IViewState {
blocks: number[][] // 0 / 1
}
初始化数据,默认全部填充0
const defaultState: () => IViewState = () => {
return {
blocks: Array(25).fill(1).map(() => {
return Array(25).fill(0)
}),
};
};
-
画点方法
点击矩阵之后,需要填充对应的格子,这里使用一个draw方法实现
draw(x: number, y: number) {
this.setState(s => {
s.blocks[x][y] = s.blocks[x][y] === 0? 1 : 0
})
}
- 渲染层
-
-
直接双层循环渲染网格即可,在对应的网格上绑定draw方法
-
{editorP.state.blocks.map((u, x) => {
return <div className='editor-row'>{
u.map((v, y) => {
return <div onClick={() => {
editorP.draw(x, y)
}} className={`editor-block ${v ? 'selected' : ''}`}></div>;
})
}</div>;
})}
游戏区
-
游戏的基本逻辑在上一篇中有描述,这里就不在重复赘述,感兴趣的可以看看juejin.cn/post/714572…
-
渲染卡片initCards
根据地图编辑器的数据来初始化游戏区需要的数据,具体逻辑看下面代码👇🏻
initCards(blocks = mockBlocks) {
const levels = 3 // 需要是3的倍数
// 已勾选的网格
let n = 0
blocks.forEach(u => {
u.forEach(v => {
if (v === 1) {
n += 1
}
})
})
let nums = n * levels
// 卡片的类型
const types = Array(nums / levels)
.fill(0)
.map((_, i) => {
return i % 7;
});
// 生成基本卡组
const cards: Card[] = [];
for (let i = 0; i < nums; i++) {
const element = new Card(types[i % (nums / levels)]);
cards.push(element);
}
// 洗牌
const data = this.shuffle(cards);
function getCard() {
const c = data.pop();
return c;
}
// 初始化层级关系
for (let i = 0; i < levels; i++) {
this.layer.init(
blocks.map(v => {
return v.map(u => {
return u === 1 ? getCard() : 0;
})
}),
i * 4,
i * 6,
levelZ(i + 1),
);
}
this.allCard = cards;
}
状态管理
-
基类只有一个update方法,在useClass调用之后会覆写
class ForceUpdateState {
update() {}
}
-
传入一个ForceUpdateState实例,给这个实例绑定update的方法,来达到触发页面渲染的功能
function useClass<T extends ForceUpdateState>(instance: T) {
const [_, update] = useState(0)
const ref = useRef(instance)
ref.current.update = useCallback(
() => {
update(s => s + 1)
},
[],
)
return ref.current
}
使用方式,需要先实例化对象,然后在react组件中使用useClass获取实例
class GamePresenter extends ForceUpdateState {...}
const gamePresenter = (new GamePresenter(new CardSlot(), new Layer()))
.
.
.
export default function HomePage() {
const p = useClass<GamePresenter>(gamePresenter)
.
.
.
当然,如果使用clean-js来管理状态,那就更加简洁方便了,还支持依赖注入功能, 具体API参考lulusir.github.io/clean-js/ap…
@injectable()
export class GamePresenter extends Presenter<IViewState> {
constructor(public slot: CardSlot, public layer: Layer) {
super();
this.state = defaultState();
}
.
.
.
export default function HomePage() {
const { p, s } = usePresenter(GamePresenter);
github.com/lulusir/mal… 期待宝子们的star⭐️
其他文章 什么?在React中也可以使用vue响应式状态管理 clean-js | 自从写了这个辅助库,我已经很久没有加过班了… clean-js | 在hooks的时代下,使用class管理你的状态 clean-js | 手把手教你写一个羊了个羊麻将版 写给前端的数据库入门 | 序 写给前端的数据库入门 | docker & 数据库 有没有一种可能,你从来都没有真正理解async 三分钟实现前端写JAVA这件事——装环境 三分钟实现前端写JAVA这件事——VS code
转载自:https://juejin.cn/post/7159106641692983327