flappybird启动

游戏启动的思路

  • 生成实例: 先要获取这些元素 dom,天空、大地、小鸟,然后生成天空实例、大地实例、小鸟实例,也要生成生成水管对类的实例

  • 开始游戏: 在开始游戏的方法里面用同一个计时器去启动这些实例的move方法

  • 暂停游戏: 清除掉总计时器、小鸟翅膀暂停煽动、水管生成器暂停生成水管对

  • 重启游戏: 清除掉总计时器、小鸟翅膀暂停煽动、水管生成器暂停生成水管对、水管生成器初始化、原先的实例全部注销、再重新生成实例

  • 用户操作: 绑定一些事件,去触发开始游戏、暂停游戏、重启游戏、小鸟跳跃

    • 其中小鸟的跳跃需要注意下,这里监控了单次点击跳跃长按空格连续跳跃

实现代码

点击 展开/收起 jsx 代码
import { useLayoutEffect, useEffect, useState, useRef } from 'react';
import Sky from './components/Sky';
import Ground from './components/Ground';
import Bird from './components/Bird';
import PipePareProducer from './components/PipePareProducer';
import './index.css';

function Game() {
  const isJumpingRef = useRef(false); // 用于追踪空格键是否被按住
  const jumpIntervalRef = useRef(null);

  const intervalRef = useRef(null);
  const skyRef = useRef(null);
  const groundRef = useRef(null);
  const birdRef = useRef(null);
  const pairPipeRef = useRef(null);
  const pairPipeProducerRef = useRef(null);

  const [gameover, setGameover] = useState(false);

  const startGame = () => {
    if (gameover) {
      return;
    }
    if (intervalRef.current) {
      return;
    }
    intervalRef.current = setInterval(() => {
      if (skyRef.current) {
        skyRef.current.move(16 / 1000);
      }
      if (groundRef.current) {
        groundRef.current.move(24 / 1000);
      }
      if (birdRef.current) {
        birdRef.current.startSwing();
        birdRef.current.move(16 / 1000);

        // 检测小鸟是否坠地
        if (birdRef.current.isHitGround()) {
          endGame();
          return;
        }
      }
      if (pairPipeRef.current) {
        // pairPipeRef.current.move(16 / 1000);
      }
      if (pairPipeProducerRef.current) {
        pairPipeProducerRef.current.startProduce();
        pairPipeProducerRef.current.pairs.forEach((pair) => {
          pair.move(16 / 1000);

          // 检测小鸟是否撞到柱子
          if (pair.isHit(birdRef.current)) {
            endGame();
            return;
          }
        });
      }
    }, 16);
  };

  const pauseGame = () => {
    if (gameover) {
      return;
    }
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
    // 小鸟停止翅膀
    birdRef.current.stopSwing();
    // 停止生成柱子
    pairPipeProducerRef.current.stopProducer();
  };

  // 重新设置游戏
  const resetGame = () => {
    // 清掉 计时器
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
    // 小鸟停止翅膀
    birdRef.current.stopSwing();
    // 停止生成柱子
    pairPipeProducerRef.current.stopProducer();
    // 删除全部柱子
    pairPipeProducerRef.current.initProducer();
    // 清理掉ref
    clearDoms();
    // 重新挂载dom
    setDoms();
    setGameover(false);
  };

  const endGame = () => {
    pauseGame();
    setGameover(true);
  };

  // 挂载dom
  const setDoms = () => {
    const skyDom = document.querySelector('.sky');
    if (skyDom) {
      skyDom.style.left = 0;
      skyRef.current = new Sky(skyDom);
    }
    const groundDom = document.querySelector('.ground');
    if (skyDom) {
      groundDom.style.left = 0;
      groundRef.current = new Ground(-100, groundDom);
    }
    const birdDom = document.querySelector('.bird');
    if (birdDom) {
      birdDom.style.top = '120px';
      birdRef.current = new Bird(birdDom);
    }
    const gameDom = document.querySelector('.game');
    if (gameDom) {
      // pairPipeRef.current = new PipePare(-100, gameDom);
      pairPipeProducerRef.current = new PipePareProducer(-100, gameDom);
    }
  };

  // clear 挂载dom
  const clearDoms = () => {
    skyRef.current = null;
    groundRef.curren = null;
    birdRef.current = null;
  };

  const birdJump = () => {
    if (birdRef.current) {
      birdRef.current.jump();
    }
  };

  useLayoutEffect(() => {
    setDoms();

    // 清除定时器
    return () => {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    };
  }, []);

  // 添加空格键事件监听,按空格触发小鸟的jump
  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.code === 'Space' && birdRef.current && !isJumpingRef.current) {
        isJumpingRef.current = true;
        // 开始持续调用 jump 方法
        jumpIntervalRef.current = setInterval(() => {
          birdRef.current.jump();
        }, 50); // 每 50 毫秒调用一次 jump
      }
      if (event.code === 'KeyO') {
        startGame(); // 按下 O 键开始游戏
      }
      if (event.code === 'KeyP') {
        pauseGame(); // 按下 P 键暂停游戏
      }
      if (event.code === 'KeyR') {
        resetGame(); // 按下 R 键重启游戏
      }
    };

    const handleKeyUp = (event) => {
      if (event.code === 'Space') {
        isJumpingRef.current = false;
        // 停止 jump 方法的持续调用
        clearInterval(jumpIntervalRef.current);
        jumpIntervalRef.current = null;
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('keyup', handleKeyUp);

    // 清除事件监听器
    return () => {
      window.removeEventListener('keydown', handleKeyDown);
      window.removeEventListener('keyup', handleKeyUp);
    };
  }, []);

  return (
    <>
      <div className="game">
        {gameover && (
          <div className="gameover">
            <div className="gameover-text"></div>
          </div>
        )}
        <div className="sky"></div>
        <div className="bird swing3"></div>
        <div className="ground"></div>
      </div>
      <div className="tip">O
        <span className="clickable" onClick={() => startGame()}>
          开始游戏
        </span>P<span className="clickable" onClick={() => pauseGame()}>
          暂停游戏
        </span>R<span className="clickable" onClick={() => resetGame()}>
          重启游戏
        </span>按 空格
        <span className="clickable" onClick={() => birdJump()}>
          触发小鸟的跳跃
        </span>
      </div>
    </>
  );
}

export default Game;