实现一个超级马里奥兄弟3代迷你抽奖小游戏
啊哈哈,大家好哇,最近在网上冲浪的时候,看到 codeopen 上有人用 react 实现了一个 马里奥兄弟3代的小游戏关卡。 那么,左右闲来无事,我们就来实现一下这个小游戏吧!
既然说那个大神都写了,为啥我还要写呢?那是因为他是用类式组件实现的,一些方法在新版的react中弃用了,所以我就用函数式组件改造了一下。
游戏流程描述
- 盒子中有三行图片,相互通过定时器对向而行
- 操作者控制使其分别停在具体的某个位置
- 通过三个位置来判断是否获取奖励
游戏规则
老虎机游戏,说白了就是拼凑图嘛,图片拼对了就能奖励。奖励规则如下:
规则 | 奖励 |
---|---|
三个全无敌星 | 5UP |
三个全花 | 3UP |
三个全蘑菇 | 2UP |
其他 | 无奖励 |
实现定时器滚动
图片滚动的效果是通过设置一个 interval 来动态的改变滑动 div 的样式来控制的:
if (props.isRunning && props.direction === 'ltr') {
// hooks 在 setInterval中设置state, 必须是回调形式,不然不生效
const newValue = props.value < 2 ? props.value + 1 : 0;
setValue(value => newValue);
setRotatingValue(props.index, newValue);
} else if (props.isRunning && props.direction === 'rtl') {
const newValue = props.value > 0 ? props.value - 1 : 2;
setValue(value => newValue);
setRotatingValue(props.index, newValue);
} else {
clearCounterInterval(props.timer);
}
在 行元素循环遍历过程中,以此判断每一行 的 isRunning 是否还在,若在,就设置 当前行的 value, value是我定义的一个分值,用于最后算奖励。 该定时器回调函数,每执行一次,就会改一次样式,我将每一行的对应 value + 1,这样可以确保最后的相同图案有相同的 value,前提是三张图片的花、蘑菇、无敌星的顺序一致:
每次会改变动画的样式:
style={{
animationName: direction + '-transition-' + value,
animationDuration: speed + 'ms',
}}
@keyframes ltr-transition-0 {
0%{
background-position: 0vw;
}
100%{
background-position: 33.3333vw;
}
}
...
@keyframes ltr-transition-2 {
0%{
background-position: 66.6666vw;
}
100%{
background-position: 100vw;
}
}
通过设置 background-position 从 0 到 100% 来模拟一个无限滚动。
监听用户操作
用户操作说明:
终端 | 按键 |
---|---|
PC | 空格键停止一行或重置游戏 |
移动端 | 触摸屏幕停止一行或重置游戏 |
通过在组件初始化时,加一个 keypress/touchstart 监听事件,在该事件内,将当前移动游标表示的行的 isRunning 置为 false,然后再定时器循环中就可以判断:
else {
clearCounterInterval(props.timer);
}
...
const clearCounterInterval = (timer) => {
clearInterval(timer);
};
计分奖励
这个就很简单,在三个都停止时,看一下三个行的 value 是不是都一样,然后再根据上面的规则显示奖励就 ok 啦!
函数式组件的坑
这里还是要说明一下,将类式组件改为函数式中遇到的两个坑。
定时器回调函数使用 state 的值
如下的代码:
const interval = setInterval(counterIntervalFunction, speed);
在 counterIntervalFunction 想要访问当前组件的 state,发现拿不到最新的,需要这样处理一下:
const [value, setValue] = useState(0);
const propsRef = useRef({
value,
});
useEffect(() => {
propsRef.current = {
value,
}
}, [value]);
通过一个 ref 过渡一下,来获取值的变化即可。在 counterIntervalFunction 中可以通过 propsRef.current 来获取状态。
同时,在定时器回调中,设置 state 也要使用回调的形式:
setValue(value => props.value > 0 ? props.value - 1 : 2);
事件的声明
在 useEffect 中声明事件时,一定要记得定义组件回调函数销毁时的解绑定:
useEffect(() => {
// 移动端 - 触摸屏幕
document.body.addEventListener('touchstart', handleClick);
// pc端 - 空格键
window.addEventListener('keypress', handleClick);
return () => {
document.body.removeEventListener('touchstart', handleClick);
window.removeEventListener('keypress', handleClick);
};
}, [handleClick]);
函数式组件在执行一次后会销毁重新创建,这里就会重新绑定事件,如果你不解绑之前的,多次空格触发事件后就会造成内存泄漏,页面卡死。
最终实现效果:
整体的过程就这么多哈,下面直接上代码来玩一玩!
喜欢的还请点个赞啦!thank U!
转载自:https://juejin.cn/post/7152511909336449060