likes
comments
collection
share

实现一个超级马里奥兄弟3代迷你抽奖小游戏

作者站长头像
站长
· 阅读数 24

啊哈哈,大家好哇,最近在网上冲浪的时候,看到 codeopen 上有人用 react 实现了一个 马里奥兄弟3代的小游戏关卡。 那么,左右闲来无事,我们就来实现一下这个小游戏吧!

既然说那个大神都写了,为啥我还要写呢?那是因为他是用类式组件实现的,一些方法在新版的react中弃用了,所以我就用函数式组件改造了一下。


游戏流程描述

  1. 盒子中有三行图片,相互通过定时器对向而行
  2. 操作者控制使其分别停在具体的某个位置
  3. 通过三个位置来判断是否获取奖励

游戏规则

老虎机游戏,说白了就是拼凑图嘛,图片拼对了就能奖励。奖励规则如下:

规则奖励
三个全无敌星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,前提是三张图片的花、蘑菇、无敌星的顺序一致:

实现一个超级马里奥兄弟3代迷你抽奖小游戏

每次会改变动画的样式:

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!