Day 16 - 用 canvas 做射击小游戏

import { useEffect, useState, useRef } from "react";
import "./styles.css";
import { playerImg, enemyImage, boomImg } from "./images";
import { forEach } from "lodash";
import usePlayer from "./usePlayer";
import useBullet from "./useBullet";
import useEnemy from "./useEnemy";
const canvasWidth = 500;
const canvasHeight = 500;

let startTimer = null;
const FPS = 31;

export default function App() {
  const [score, setScore] = useState(0);
  const [playerBullets, setPlayerBullets] = useState([]);
  const [playerEnemys, setPlayerEnemys] = useState([]);
  const playerImgRef = useRef(null);
  const enemyImgRef = useRef(null);
  const enemyBoomImgRef = useRef(null);
  const canvasRef = useRef(null);
  const [ctx, setCtx] = useState(null);

  useEffect(() => {
    console.log("playerEnemys", playerEnemys);
  }, [playerEnemys]);

  const endGame = () => {
    clearInterval(startTimer);
    setScore(0);
    setPlayerBullets([]);
    setPlayerEnemys([]);
    playerImgRef.current.img = playerImg;

    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  };

  useEffect(() => {
    if (canvasRef.current) setCtx(canvasRef.current.getContext("2d"));
  }, [canvasRef]);

  const { bullet, drawBullet, explodeBullet, setBullet } = useBullet({ ctx });

  const { player, drawPlayer, updatePlayer, explodPlayer } = usePlayer({
    endGame,
    ctx,
    setPlayerBullets,
    setBullet,
    bullet,
    playerImgRef
  });

  const { E, setE, explodeE, drawE, enemyImg, active } = useEnemy({
    ctx,
    enemyImgRef
  });

  const startGame = () => {
    update();
    // draw();
    // if (ctx) {
    //   startTimer = setInterval(function () {
    //     update();
    //     draw();
    //   }, 1000 / FPS);
    // }
  };

  const draw = () => {
    if (ctx) {
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
      drawPlayer();
      forEach(playerBullets, (bullet) => {
        drawBullet();
      });
      forEach(playerEnemys, (enemy) => {
        drawE();
      });
    }
  };

  const update = () => {
    forEach(playerBullets, (bullet, index, object) => {
      setBullet((prev) => {
        return { ...prev, y: prev.y - prev.speed };
      });
      if (bullet?.y < 0 || bullet?.active) {
        object.splice(index, 1);
      }
    });
    forEach(playerEnemys, (enemy, index, object) => {
      setE((prev) => ({ ...prev, y: prev.y + enemy.speed }));
      if (enemy.y > canvasHeight || enemy.active) {
        setScore((prev) => prev + 1);
        object.splice(index, 1);
      }
    });
    console.log(Math.random(), Math.random() < 0.02);
    if (Math.random() < 0.5) {
      console.log("add");
      setPlayerEnemys((prev) => [...prev, { ...E }]);
    }
    handleCollisions();
  };

  const handleCollisions = () => {
    forEach(playerBullets, (bullet) => {
      forEach(playerEnemys, (enemy) => {
        if (collides(bullet, enemy)) {
          explodeBullet();
          explodeE();
        }
      });
    });

    forEach(playerEnemys, (enemy) => {
      if (collides(enemy, player)) {
        explodeE();
        explodPlayer();
      }
    });
  };

  const collides = (a, b) => {
    return (
      a.x < b.x + b.width &&
      a.x + a.width > b.x &&
      a.y < b.y + b.height &&
      a.y + a.height > b.y
    );
  };

  return (
    <div className="App">
      <button className="startBtn" onClick={startGame}>
        Start
      </button>
      <div> Score:{score}</div>
      <br />
      <canvas
        ref={canvasRef}
        width={canvasWidth}
        height={canvasHeight}
      ></canvas>
      <img alt="" className="playerImg" ref={playerImgRef} src={playerImg} />
      <img alt="" className="enemyImg" ref={enemyImgRef} src={enemyImage} />
      <img
        alt=""
        className="enemyBoomImg"
        ref={enemyBoomImgRef}
        src={boomImg}
      />
    </div>
  );
}

待完成
https://codesandbox.io/s/tie-ren-sai-she-ji-you-xi-g0kob?file=/src/App.js:0-3833


<<:  成为工具人应有的工具包-16 ChromeCacheView

>>:  [Day 16] Reverse 小疲累

Day8-流程控制表达

第四章也蛮简单的,Böhm与Jacopini证明所有程序都可使用三种流程控制表达 执行一个子程序,然...

AE-LED流动效果2-Day20

接续昨天的练习~ 1.然後我们再新增一个Comp,这次把高度改为1080px 2.并把刚刚的Comp...

Day11:Swift 基础语法 —Array

前言 上一篇文章讲到 Dictionary, 今天讲另一个值的集合 - Array。 Array 和...

Day4 Android - Layout版面(上)

接续前一天提到的,每一种layout都有它不同的属性名称以及排序模式,我这边就先举三个较常用的lay...

[Day06]程序菜鸟自学C++资料结构演算法 – 常见的线性串列其一:链结串列Linked List

前言:讨论完阵列之後接着就要来看看它的好兄弟链结串列Linked List,在Day03的文章中有提...