Creating game for Android using JavaScript #3 | Events and gameplay
Read also:
Creating game for android using JavaScript #1 | How to start
Creating game for android using JavaScript #2 | Drawing game objects
—
Mouse and Touch Events
It’s time to make our game playable. What we need to do, is to give our hero little boost after clicking or touching the screen. Problem is that many new devices such as smartphones, tablets or even new laptops use touch screens instead of mouse. For user this is even better but for developer it could be problematic because touching the screens generates different events that using mouse. For more complex game it’s little bit more complicated to create common events API for both type of controllers. In example when we are using touch screens we can generate many touches at the same time, what is not possible by single mouse pointer. But fortunately we are lucky and our FlappyHero needs only simple “tap” event.
Order of events after single ‘tap’:
- touchstart
- touchmove
- touchend
- mouseover
- mousemove
- mousedown
- mouseup
- click
Create new js file events.js with Events class. Tapping the screens is similar to clicking by mouse so we can use only mouse events or bind same handlers for different events.
constructor (game) {
this.game = game;
let canvas = document.getElementsByTagName('canvas')[0];
//canvas.addEventListener('touchstart', this.touchStart.bind(this), false);
canvas.addEventListener('mousedown', this.touchStart.bind(this), false);
//canvas.addEventListener('touchend', this.touchEnd.bind(this), false);
canvas.addEventListener('mouseup', this.touchEnd.bind(this), false);
}
We are able to bind same handler for both touchstart and mousedown, same as touchend and moseup. But if we do this we have to use e.preventDefault(); to stop propagate touch event if it exist. We could also check if touch events are available on current device and bind them instead of mouse events.
Our touch handler has two tasks. One is to (re)start the game if menu is opened, and the second is for giving our hero a boost. By default boost should be disabled by touchEnd handler. But to prevent user for endless touching, there is additionally setInterval timer which should stop booster after while. That will force gamer to taping the screen.
touchStart (e) {
e.preventDefault();
clearInterval(this.clearTimer);
if (this.game.isGame()){
this.game.flappy.setBoost(true);
} else if(this.game.isMenu() || this.game.isGameOver() ){
this.game.reset();
}
this.clearTimer = setInterval(this.stopBoost.bind(this), 150);
}
Gameplay Flags
To manage our game we have to have few state flags. I’ve decided that four will be enough. They are defined in Game class constructor.
this.state = Object.freeze({GAME: 1, MENU: 2, DYING : 3, GAME_OVER: 4});
Names are quite readable. Based on current game state defined in gameState field, we are able to control our gameplay. Now we can decided what should be draw on the screen during the game and what after game is over. What objects should be update and in which way. In example when our hero hits the obstacle, game switch to DYING flag and our hero is falling down and obstacles are not moving anymore. When GAME state is set, then we are drawing all game elements, but when MENU and GAME_OVER state is set, then obstacles are not displayed at all, and we displays simple square with game title or text information.
Gameplay calculations
The main aim of flappy gameplay is to overtaking moveable obstacles. To do this we are boosting our hero to give him power to flight. Without this power he just falls to the ground. Boosting event was described before. Now focus on Flappy update method.
update (modifier, flappyOnTheGround, dying) {
let {boost, config, width, height} = this;
let gravity = config.flappy.gravity;
if (dying) {
gravity = config.flappy.hitGravity;
}
if (boost) {
this.y = this.y - config.flappy.boost / modifier;
if (this.y < 0) {
this.y = 0;
}
} else {
this.y = this.y + gravity / modifier;
if ( this.y > config.height - height) {
flappyOnTheGround();
}
}
}
When boost is disabled our hero is attracted by the gravitation, and hero is falling down. We just simple added some gravity parameter to our Y position (0 is at the top of the screen). When hero hits the ground flappyOnTheGround callback method is called and state is switched to GAME_OVER.
When our touch enables boost, hero gain power and we subtract boost parameter from Y position. Of course we also use modifier to ensure regular gameplay. Additionally when game is in DYING state (after hitting obstacle) we multiply gravity and our hero falls immediately to the ground.
Detecting Collisions
So how to check if hero hits the obstacle? It’s really simple. Our obstacle is made from two rectangles, and our hero is also rectangle. So we have to check if two rectangles are overlapping or not. Indeed it is easier to check if they are not overlapping and then negate this statement.
So let’s check if the left edge of Flappy is after right edge of obstacle, or left edge of obstacle is after right edge of Flappy. For rectangles we should do the same for top and bottom edges, but in our case it’s even easier because our obstacles are limited by the top and bottom of the canvas.
//check collisions
this.obstacles.forEach((obs)=> {
let isFlappyAfterObstacle = flappy.x >= obs.x + obs.width,
isFlappyBeforeObstacle = obs.x >= flappy.x + flappy.width,
isOnTopObstacle = flappy.y < obs.top,
isOnBottomObstacle = flappy.y + flappy.height > obs.bottom;
if(!(isFlappyAfterObstacle || isFlappyBeforeObstacle) &&
(isOnTopObstacle || isOnBottomObstacle)) {
this.gameState = state.DYING;
}
});
Game Score
After setting game to GAME_OVER state it is good practice to show the current and best score. Current score is hold in Game object. When hero passes obstacle, score field is incremented. We can also show its value during the game. Checking if hero passes obstacle is implemented in update method of Obstacle class.
if (this.x + width < config.flappy.position && !this.scored) {
updateScore();
this.scored = true;
}
Additionally best score can be hold in HTML 5 local storage and update after game ends.
gameOver () {
if (!localStorage.bestScore || this.score > localStorage.bestScore ){
localStorage.bestScore = this.score;
}
this.gameState = this.state.GAME_OVER
}
To be continued…