Я пытаюсь построить игру космического захватчика с нуля, используя React / React-Hook и холст HTML5.
Пока мне удалось нарисовать свой корабль на холсте, но я не могу понять, какчтобы получить доступ к моим состояниям в функции requestAnimationFrame. Мне удалось получить доступ к ссылкам, но я не хочу, чтобы все мои переменные были ссылками.
Пока мой код выглядит так:
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"в функции потока, но она всегда возвращает" ноль "(значение состояния по умолчанию),
Я обнаружил, что вам нужно связать контекст с функцией animate, но я не знаю, как это сделать. это с функциональным компонентом (с компонентом класса я бы сделал .bind (this))
Я довольно новичок в HTML5 canvas, поэтому я не смогу увидеть проблему здесь.
Все советы приветствуются,
Заранее спасибо!