Day 15 - 用 canvas 做打弹珠

import "./styles.css";
import useBall from "./hooks/useBall";
import useBoard from "./hooks/useBoard";
import React, { useEffect, useState } from "react";

const width = 500;
const height = 500;
const refreshInterval = 50;
//建立一个小球
const ballRadius = width / 30;
const ballX = width / 2 - ballRadius;

//建立一个挡板
const boardWidth = width / 6;
const boardHeight = 10;
const boardX = (width - boardWidth) / 2;
const boardY = height - 100;

export default function App() {
  const [started, setSarted] = useState(false);
  const [stateCode, setSateCode] = useState(0);
  const [timer, setTimer] = useState(null);
  const [context, setConText] = useState(null);

  const { board, reset: resetBoard, drawMe: drawBoard, setBoard } = useBoard(
    boardX,
    boardY,
    boardWidth,
    boardHeight,
    context
  );

  const {
    ball,
    move: moveBall,
    drawMe: drawBall,
    reset: resetBall,
    setBall
  } = useBall(ballX, 50, ballRadius, context);

  console.log("board", board);

  useEffect(() => {
    const canvas = document.getElementById("canvas");
    if (canvas) {
      const ctx = canvas.getContext("2d");
      setConText(ctx);
    }
  }, [stateCode]);

  useEffect(() => {
    if (context) startGame();
  }, [context]);

  /** 执行开始游戏 */
  const handleStartGameBtnClick = () => {
    setSateCode(1);
  };

  const clear = () => {
    if (context) {
      context.clearRect(0, 0, width, height);
    }
  };

  const startGame = () => {
    resetBall();
    resetBoard();
    refreshGameView();
    const updateTimer = setInterval(refreshGameView, refreshInterval);

    setTimer(updateTimer);
    setSarted(true);
  };

  const refreshGameView = () => {
    /** 重新整理游戏区域 */
    //每次重新整理前都需要清除背景,不然小球和挡板上次的位置会被保留
    clear();

    const { x, y } = moveBall();

    var ballX = x;
    var ballY = y;
    if (ballX < 0) {
      setBall((prev) => ({ ...prev, x: 0, speedX: prev?.speedX * -1 }));
    }
    if (ballY < 0) {
      setBall((prev) => ({ ...prev, y: 0, speedY: prev?.speedY * -1 }));
    }
    if (ballX + 2 * ball.radius > width) {
      setBall((prev) => ({
        ...prev,
        x: width - 2 * ball?.radius,
        speedY: prev?.speedX * -1
      }));
    }
    const ballBottomY = y + 2 * ball.radius;
    const ballCenterX = x + ball.radius;
    if (ballBottomY >= board.y) {
      if (ballCenterX >= board.x && ballCenterX <= board.x + board.width) {
        //反弹
        setBall((prev) => ({
          ...prev,
          speedY: prev?.speedY * -1
        }));
      } else {
        //游戏结束
        if (timer != null) {
          clearInterval(timer);
          setSateCode(-1);
        }
      }
    }

    //画小球和挡板
    // drawBall();
    // drawBoard();
  };

  const handleMouseMove = (event) => {
    /** 处理滑鼠的移动事件,移动滑鼠的同时移动挡板 */
    const x = getClientOffset(event)?.x;
    //将挡板的水平中心位置移到x处
    let boardX = x - board?.width / 2;
    if (boardX < 0) {
      boardX = 0;
    }
    if (boardX + board?.width > width) {
      boardX = width - board?.width;
    }
    setBoard({ ...board, x: boardX });
  };

  /** 取得位置 */
  const getClientOffset = (event) => {
    const canvas = document.getElementById("canvas");
    let rect = canvas.getBoundingClientRect();
    const point = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top
    };
    return point;
  };

  /** 游戏结束 */
  const renderGameOverView = () => (
    <div>
      <p>游戏结束</p>
      <button className="start-game-btn" onClick={handleStartGameBtnClick}>
        开始游戏
      </button>
    </div>
  );

  /** 未开始 */
  const renderStartView = () => (
    <button className="start-game-btn" onClick={handleStartGameBtnClick}>
      开始游戏
    </button>
  );

  /** 游戏中 */
  const renderGameView = () => (
    <canvas
      id="canvas"
      onMouseMove={handleMouseMove}
      width={width}
      height={height}
    />
  );

  const stateCodeHtml = {
    "-1": renderGameOverView() /** 游戏结束 */,
    0: renderStartView() /** 游戏开始 */,
    1: renderGameView() /** 游戏画面 */
  };

  return (
    <div className="App">
      <div id="container">{stateCodeHtml[stateCode]}</div>
    </div>
  );
}


<<:  给别人前先包装:套件、汇入、存取修饰词 Packages, imports and Visibility modifiers

>>:  资安生活的日常

{DAY 20} Pandas 学习笔记part.6

前言 这篇文章会进行到更多的资料操作 将会处理 Indexing Values 在标签值的处理很重...

Day 29 SQLite资料库

(一)介绍 当我们需要对资料进行大量递增、删、改、查操作时,我们就会使用到资料库,而最广泛被应用的就...

Python & SQLALchemy 学习笔记_查询

由於查询部分的篇幅相较於前几者较多,因此将查询的部分独立出来写 另外这边写的只有一些基础的操作,像是...

电子书阅读器上的浏览器 [Day19] 翻译功能 (I) 支援 Onyx 内建翻译

有些时候需要看外文网页,而外文里可能有许多单字,或是看外文的速度没有那麽快,阅读起来会很辛苦。这时如...

Day14 javascript 错误

今天要来看的是JavaScript 错误 - throw、try 和 catch: 1.try 语句...