无法在我的requestAnimationFrame函数中访问我的状态

时间:2019-11-02 17:09:51

标签: reactjs html5-canvas react-hooks

我正在尝试使用React / React-Hook和HTML5画布从头开始构建太空侵略者游戏。

到目前为止,我已经实现了将自己的船绘制在画布上的功能,但是我不知道如何在“ requestAnimationFrame”函数中访问自己的状态。我确实成功访问了REF,但是我不希望我的所有变量都成为REF。

到目前为止,我的代码如下:

import React from 'react';
import {makeStyles} from '@material-ui/core/styles';

const spaceInvaderStyles = makeStyles((theme) => ({
  canvas: {
    display: 'block',
    margin: 'auto',
    imageRendering: 'optimizeSpeed',
    imageRendering: '-moz-crisp-edges',
    imageRendering: '-webkit-optimize-contrast',
    imageRendering: 'optimize-contrast',
    backgroundColor: 'black',
  }
}))



// GAME CONSTANTS
const GAME = {
  shape: {
    w:'640px',
    h:'640px',
  },
  shipRow: '600',
  shipColor: 'rgba(0,252,0)',
  spritesSrc: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAADuCAYAAABh/7RrAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAM4SURBVHhe7ZhRctswDAWrHCH3P6OPkNTuqB0FwxEAEqTkvt0feyYUJCRvX5xsvwr4frK/7WJ7sr9dxsf+KgeLqyG7eLNURstqNhVlSNTV0HX87j5HyXpP1NXA8QyeT97M0etbeDMtRF0NHH93cDwIi6uR8uIvXi94vo1eXwFRVwPHz8g6Oft8BURdDV3HPb9ezHAsw4xnJOpq6C5+tb8V9OzQtXSkbEZY8cPAcTVYXI1QicwuM48ZZUfU1cDxI1c77VHhPFFXQ9fxHp+tY6OdUDHPzvAg6mrg+LuD40FYXI2mF5731qfRnsjOs+d7IOpq6Doe8bPCqRFmPCNRV0PX8f31FM+x0Q6YPb8FUVeDxdXoKg2vjEaZUWYWoq4Gi6tR8sltNnxyK4TF1Wi6c7XTHhXOE3U1dB3v8dk6NtoJFfPsDA+irgaOvzs4HoTF1ej6zGt7IeuXpXpeBKKuBo4fsc5ZrIPeeY/sPHu+B6Kuhq7jET+rHcxe751/4d3TQtTV0F0868Yd6dmhZOlI+ZxxxTcfx9VgcTWapTJaVrOpKEOiroau43f3OUrWe6KuBo5n8HzyZo5e38KbaSHqauD4u4PjQVhcjZAX2R7wfKue1wNRVwPHj3gOjjo8+/oIRF0NHD9jtnMrnLYQdTVYXI2ucptRNkdW3I+oq8HiajT/y+qVSXX5ZOfZ8y+yz0DU1cDxI9aX1pmVRJ7HnvEg6mrgeIaIcxkq5tkZHkRdDRx/d3A8CIur0fTi7t5nfW5B1NXA8TOs8xWOnbHifkRdDRw/stppjxnPQ9TVYHE1QiWxuuxW3I+oq8HianSVRnX5ZOc9j3/ub//xvOSxvw1B1NXA8TOsg6sZ7ZAWRF0NHD/iOW2dG+2A7Dx7vgeiroau46N+3oWs90RdDRw/Yn25ugciz2PPeBB1NXD8iOdU1qcs3v3s119kn4moq6Hr+P6aouXYEc+30esrIOpqsLga00ukxcf3549y+9oey5+DqKvB4mosKRVbZh4ryo6oq8HiarC4GrKLN39fZn/v3p3W5wKiroau4/+bz1GIuhosrgaLq8HiarC4GiyuBourIbv4n/9Fqf1N/rU9tt/3UY/ZXED3UQAAAABJRU5ErkJggg==',
  sprites: {
    ship: {
      x: 0,
      y: 204,
      w: 61,
      h: 237,
    }
  },
  player: {
    initialPos: {
      x: 290,
      y: 580,
    }
  }


}

const KEYS = {
  left:81,
  right:68,
  down: 83,
  up: 90,
  arrowLeft: 37,
  arrowRight: 39,
  arrowDown: 40,
  arrowUp: 38,
}



const SpaceInvader = (props) => {

  const classes = spaceInvaderStyles();

  const canvasRef = React.useRef();

  const [cctx, setCctx] = React.useState(null);
  const [sprites, setSprites] = React.useState(null);
  const [player, setPlayer] = React.useState({
    pos: GAME.player.initialPos.x,
  })

  // keys
  const [currentKey, setCurrentKey] = React.useState(null);

  //time
  let lastTime = 0;

  const [counter, setCounter] = React.useState(0);


  React.useEffect(() => {

    // INIT

    // context
    const canvas = canvasRef.current;
    const context = canvas.getContext('2d');
    setCctx(context)

    // sprites
    loadSprites();

  }, [])

  // key handler
  React.useEffect(() => {
    window.addEventListener('keydown', (event) => handleUserKeyPress(event,true));
    window.addEventListener('keyup', (event) => handleUserKeyPress(event,false))

    return () => {
      window.removeEventListener('keydown', (event) => handleUserKeyPress(event,true));
      window.removeEventListener('keyup', (event) => handleUserKeyPress(event,false))
    };
  }, [cctx, sprites, player, currentKey])

  React.useEffect(() => {
    if(!cctx) return
    animate();
  }, [cctx])

  React.useEffect(() => {

    if(spritesAreLoaded()){
      cctx.drawImage(sprites, GAME.sprites.ship.x, GAME.sprites.ship.y, GAME.sprites.ship.w, GAME.sprites.ship.h, GAME.player.initialPos.x, GAME.player.initialPos.y , GAME.sprites.ship.w, GAME.sprites.ship.h)

    }
  }, [sprites])

  React.useEffect(() => {
    console.log(counter)
  }, [counter])

  // utils
  const clearCanvas = () => {

    cctx.clearRect(0,0, 640, 640);
  }

  const saveCanvas = () => {
    cctx.save();
  }

  const drawImage = (image, sx, sy, sw, dx, dy, sh, dw, dh) => {
    cctx.drawImage(image, sx, sy, sw, dx, dy, sh, dw, dh);
  }

  const restore = () => {
    cctx.restore();
  }

  const loadSprites = () => {
    var spritesImg = new Image();
    spritesImg.src = GAME.spritesSrc;
    spritesImg.onload = function() {
      // sprites are loaded at this point
      setSprites(spritesImg);
    }

  }

  const spritesAreLoaded = () => {
    return sprites !== null;
  }

  const move = (direction) => {
    // cctx, sprites and all others state vars are at default value here too
    clearCanvas();
    saveCanvas();
    drawImage(sprites, GAME.sprites.ship.x, GAME.sprites.ship.y, GAME.sprites.ship.w, GAME.sprites.ship.h, player.pos + (10 * direction), GAME.player.initialPos.y , GAME.sprites.ship.w, GAME.sprites.ship.h);
    restore();
    setPlayer({...player, pos: player.pos + (10 * direction)});
  }



  const handleUserKeyPress = React.useCallback( (event, isDown) => {

    event.preventDefault();
    const {key, keyCode} = event;
    setCurrentKey(isDown ? keyCode : null);

  }, [cctx, sprites, player, currentKey])

  const updatePlayer = () => {
    // currentKey is at default value here...
    const direction = currentKey === KEYS.left ? -1 : currentKey === KEYS.right ? 1 : null;


    if(direction !== null) move(direction)

  }

  const animate = (time) => {

    var now = window.performance.now();
    var dt = now - lastTime;

    if(dt > 100) {
      lastTime = now;
      updatePlayer();
    };

    requestAnimationFrame(animate);


  }



  return (
    <canvas
      className={classes.canvas}
      ref={canvasRef}
      width={GAME.shape.w}
      height={GAME.shape.h}

    />
  )


}

export default SpaceInvader;

我正在尝试访问线程函数中的“ currentKey”,但它始终返回“ null”(默认状态值),

我在某个主题上发现您需要将上下文绑定到动画函数,但是我不知道如何使用功能组件(如果使用类组件,我会使用.bind(this))

我在HTML5画布上还很陌生,所以在这里我可能看不到问题。

感谢所有提示

谢谢!

1 个答案:

答案 0 :(得分:0)

最后,经过大量测试,我做到了:

React.useEffect(() => {
    console.log("use Effect");
    if(!cctx) return
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  })

它有效...

如果有人有更清洁的解决方案

编辑1:经过更多测试后,它可能不是可行的解决方案,它会导致过多的重新渲染