1
I’m making a piano mini-game similar to Guitar Hero, where blocks fall from the sky and you have to press the note (in my case, the piano note/key) equivalent at the right time, they fall in specific time to simulate the music.
Falling blocks are being programmed in Canvas.
I made a pure Javascript version and is working as I want, but I’m not able to transfer to React.
There will be a JSON object, which is the music score, important to talk about it now, is that it contains the frame number (frameNo) in which the corresponding note block should appear (at each canvas update, a frame is counted):
const cheatSheet = [
{
"note": {
"left hand" : "F4",
"right hand":"G4"
},
"frameNo": 25
}
]
At first I have the following example musical score, in case you want to test the code:
const cheatSheet = [{"note":{"left hand":"F4","right hand":"G4"},"frameNo":25},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":50},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":75},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":100},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":125},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":150},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":175},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":200},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":225},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":250},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":275},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":300},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":325},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":350},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":375},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":400},{"note":{"left hand":"D4","right hand":"A4"},"frameNo":425},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":450},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":475},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":500},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":525},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":575},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":600},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":625},{"note":{"left hand":"B4","right hand":"D4"},"frameNo":650},{"note":{"left hand":"E4","right hand":"A4"},"frameNo":675},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":700},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":725},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":750},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":775},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":800},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":825},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":850},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":875},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":900},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":925},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":950},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":975},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1000},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1025},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1050},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1075},{"note":{"left hand":"D4","right hand":"A4"},"frameNo":1100},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1125},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":1150},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":1200},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":1300}];
My code in pure Javascript is like this:
var cheatSheet = [{"note":{"left hand":"F4","right hand":"G4"},"frameNo":25},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":50},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":75},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":100},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":125},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":150},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":175},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":200},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":225},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":250},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":275},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":300},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":325},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":350},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":375},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":400},{"note":{"left hand":"D4","right hand":"A4"},"frameNo":425},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":450},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":475},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":500},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":525},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":575},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":600},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":625},{"note":{"left hand":"B4","right hand":"D4"},"frameNo":650},{"note":{"left hand":"E4","right hand":"A4"},"frameNo":675},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":700},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":725},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":750},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":775},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":800},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":825},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":850},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":875},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":900},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":925},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":950},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":975},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1000},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1025},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1050},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1075},{"note":{"left hand":"D4","right hand":"A4"},"frameNo":1100},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1125},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":1150},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":1200},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":1300}];
var gameArea = {
canvas: document.createElement("canvas"),
start : function() {
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.noteNo = 0;
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.interval = setInterval(updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop : function() {
clearInterval(this.interval);
}
}
var blocks = [];
function startGame() {
gameArea.start();
}
function Block() {
this.width = 10;
this.height = 10;
this.x = 0;
this.y = 0;
this.update = () => {
let ctx = gameArea.context;
ctx.fillStyle = 'green';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
function updateGameArea() {
gameArea.clear();
gameArea.frameNo += 1;
let currNote = gameArea.noteNo;
if (currNote < cheatSheet.length) {
let noteFrame = cheatSheet[currNote].frameNo;
let currFrame = gameArea.frameNo;
if (noteFrame === currFrame) {
blocks.push(new Block());
gameArea.noteNo += 1;
}
}
for (let i = 0; i < blocks.length; i++) {
blocks[i].y += 1;
blocks[i].update();
}
}
canvas {
border:1px solid #d3d3d3;
background-color: #f1f1f1;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body onload="startGame()">
</body>
</html>
If you take and run this code in some environment (or right here), this is how it should work.
However, when it comes to React, Canvas simply doesn’t rotate (it goes blank).
const cheatSheet = [{"note":{"left hand":"F4","right hand":"G4"},"frameNo":25},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":50},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":75},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":100},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":125},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":150},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":175},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":200},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":225},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":250},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":275},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":300},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":325},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":350},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":375},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":400},{"note":{"left hand":"D4","right hand":"A4"},"frameNo":425},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":450},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":475},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":500},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":525},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":575},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":600},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":625},{"note":{"left hand":"B4","right hand":"D4"},"frameNo":650},{"note":{"left hand":"E4","right hand":"A4"},"frameNo":675},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":700},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":725},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":750},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":775},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":800},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":825},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":850},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":875},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":900},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":925},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":950},{"note":{"left hand":"E4","right hand":"G4"},"frameNo":975},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1000},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1025},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1050},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1075},{"note":{"left hand":"D4","right hand":"A4"},"frameNo":1100},{"note":{"left hand":"D4","right hand":"B4"},"frameNo":1125},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":1150},{"note":{"left hand":"F4","right hand":"G4"},"frameNo":1200},{"note":{"left hand":"C4","right hand":"C5"},"frameNo":1300}];
function Block(gameArea) {
this.width = 10;
this.height = 10;
this.x = 0;
this.y = 0;
this.update = () => {
let ctx = gameArea.context;
ctx.fillStyle = 'green';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
class CanvasComponent extends React.Component {
constructor() {
super();
this.gameArea = {
canvas: React.createRef(),
start : function() {
this.context = this.canvas.current.getContext("2d");
this.frameNo = 0;
this.noteNo = 0;
this.blocks = [];
this.interval = setInterval(this.updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop : function() {
clearInterval(this.interval);
}
}
}
updateGameArea() {
const cheatSheet = this.props.cheatSheet;
const gameArea = this.gameArea;
gameArea.clear();
gameArea.frameNo += 1;
let currNote = gameArea.noteNo;
let blocks = gameArea.blocks;
if (currNote < cheatSheet.length) {
let noteFrame = cheatSheet[currNote].frameNo;
let currFrame = gameArea.frameNo;
if (noteFrame === currFrame) {
blocks.push(new Block(gameArea));
gameArea.noteNo += 1;
}
}
for (let i = 0; i < blocks.length; i++) {
blocks[i].y += 1;
blocks[i].update();
}
}
componentDidMount() {
const { gameArea } = this;
gameArea.start();
}
render() {
return <canvas ref={this.gameArea.canvas} width={300} height={300} />;
}
}
ReactDOM.render(<CanvasComponent cheatSheet={cheatSheet} />, document.getElementById('root'));
canvas {
border:1px solid #d3d3d3;
background-color: #f1f1f1;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
What I tried to...
Unfortunately, I haven’t tried much, because I don’t even know what to hypothesize. But I tried to simulate in another environment a simple canvas in React, and noticed that putting the setInterval in the place where it was not running, so I put it in the componentDidMount and solved the situation. This is only the first mistake, even after this change the code does not work. I tried searching the internet for examples of canvas in React to see if I didn’t make some conceptual or syntax error of something. But it’s really hard to find examples of this in React classes, not Hooks.