In the last post, I hooked up keyboard events to move around one of the game pieces. This time I’ll investigate how to make the piece rotate too.
Just like we used the SVG transform to scale and translate the pieces earlier, it can also be used for rotation.
Just to demonstrate this, I changed the code that formulates the transform to include a 5 degree rotation at the end:
var transformString = "scale("+gridSize+" "+gridSize+") translate(" + this.props.x + " " + this.props.y + ") rotate(5)"
This results in something that looks like this:
Really what we want to do is accept a rotation angle as one of the props of the BlockShape component.
var transformString = "scale("+gridSize+" "+gridSize+") translate(" + this.props.x + " " + this.props.y + ") rotate("+this.props.rotation+")"
unfortunately, if I run with this, all of the pieces are disappeared and I have a bunch of error messages saying that the transform attribute cannot be parsed. The issue is that now none of my Rectangle tags specify the rotation. And if you try to use a property that does not have a value specified for it, you get “undefined”. This brings up an important question that I have run into a couple times already but just worked around:
How do I specify a default value of a property in React.js?
This question is answered on this page http://facebook.github.io/react/docs/reusable-components.html
The answer is to implement the getDefaultProps function within the BlockShape component:
getDefaultProps: function() { return { rotation: '0' }; }
So now to add the rest of the plumbing so that I can control the rotation using the key event, I’m basically following the same steps that I did in the last tutorial:
Adding a new method to my ReactrisSVG component for rotating the current piece:
rotatePiece: function( quarterTurns ){ this.setState ({ currentPieceRot: this.state.currentPieceRot+quarterTurns*90}) }
Changing the ReactrisShape tag for the yellow piece to include the rotation:
var currentPiece = <ReactrisShape ref="currentPiece" type="0" x={this.state.currentPiecePos[0]} y={this.state.currentPiecePos[1]} rotation={this.state.currentPieceRot} color="yellow"/>
Initializing the initial state of the currentPiecePos variable in the ReactrisSVG component:
getInitialState: function() { return ({ currentPiecePos: [1,1], currentPieceRot: 0 }); },
And changing the event handler to use the keys “1” and “2” to control rotation both clockwise and counterclockwise.
charString = String.fromCharCode(e.keyCode) if (e.keyCode == '38') { context.movePiece(0,-1); } else if (e.keyCode == '40') { context.movePiece(0,1); } else if (e.keyCode == '37') { context.movePiece(-1,0); } else if (e.keyCode == '39') { context.movePiece(1,0); } else if (charString == '1') { context.rotatePiece(-1); } else if (charString == '2') { context.rotatePiece(1); }
Now this basically works (see http://jsfiddle.net/uglycoyote/z0aj3ysv/27/) but there are a couple things wrong with it:
Firstly, the piece is rotating around its corner, which causes it to swing up and out of view. Really we need to be rotating around a more central point. The trouble is when I wrote the arrays which define each of the game pieces, I used 0,0 as the upper-left corner of each piece. I’ll save this problem for later.
The more annoying problem at this point is that the key events are getting handled twice: once by the game, and then again by the browser. I noticed this a little bit with the arrow keys. They made the game piece move but also caused the browser to scroll around within the little tiny JSFiddle viewport that the game is in, which on my small laptop screen is not quite big enough to hold the entire SVG canvas. (if the monitor were bigger I might not have noticed this because the viewport wouldn’t scroll if the contents fit entirely inside).
When I started using the 1 and 2 keys, this was even more problematic. Currently i’m testing most of this under Firefox, and i have the “search when I start typing” setting turned on, which generally i find very useful, but in this case each time i press the 1 or 2 key it’s taking focus away from the game and acting as though I want to search for 1’s and 2’s in the page. However, I don’t have this problem under Chrome because Chrome does not have this feature (although I have used a plugin which implements this feature under Chrome on other machines)
Is there a way to have my event handler mark the key event as being handled so that the key event doesn’t get handled twice by two different systems?
It turns out that returning a value of false from the event handler cancels the event and prevents the browser from handling it:
if (e.keyCode == '38') { context.movePiece(0,-1); return false; } else if (e.keyCode == '40') { context.movePiece(0,1); return false; } else if (e.keyCode == '37') { context.movePiece(-1,0); return false; } else if (e.keyCode == '39') { context.movePiece(1,0); return false; } else if (charString == '1') { context.rotatePiece(-1); return false; } else if (charString == '2') { context.rotatePiece(1); return false; } return true;
Now to tackle the pesky problem of the pivot point.
Actually, adding a couple more translations on to our SVG trasform solves this. This is where my games/graphics knowledge comes in handy. Rotation around a specific pivot point is equivalent to translating so that the pivot point becomes the new origin, rotating, and then translating again by the opposite of the pivot point offset.
For that yellow S-Shape, I want the pivot point to be at the centre of the second block from the left, so at 1.5, 0.5. Here’s my new transformString:
var transformString = "scale("+gridSize+" "+gridSize+") translate(" + this.props.x + " " + this.props.y + ") translate(1.5,0.5) rotate("+this.props.rotation+") translate(-1.5,-0.5)"
I may later want to have a different pivot point for each of the different shapes. But actually this 1.5, 0.5 looks like a decent choice of pivot point for most of the shapes.
Here’s the fiddle up to this point: http://jsfiddle.net/uglycoyote/z0aj3ysv/30/
That concludes this posting. In the next blog post I will need to start looking at some kind of a mechanism for shifting the controls to focus on different game pieces.