iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Modern Web

Canvas 小錦囊系列 第 15

Day 15 - 用 canvas 做打彈珠

  • 分享至 

  • xImage
  •  
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>
  );
}


上一篇
Day 14 - 用 canvas 製作刮刮樂
下一篇
Day 16 - 用 canvas 做射擊小遊戲
系列文
Canvas 小錦囊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言