Javascript game akin to cannon shooter, pool, Asteroids, and shuffleboard
BouncyBee is a single-page game where cannon shooter meets pool and shuffleboard and Asteroids. The player launches a bee from a static spot, and the bee then bounces around the gameboard before decelerating to a stop. While the bee flies, the player has a chance to nudge its trajectory to collect points and avoid enemies.
The player earns points when the bee flies across (pollinates!) flowers, and gets extra points if the bee lands back at the beehive. The player loses if the bee depletes its health fully, which happens when it runs into the wasps that move across the gameboard.
Play it 🐝 here!
Or, watch the quick
In BouncyBee, users are able to:
- Set initial bee direction and speed, akin to cannon shooter
- Nudge the bee's flight trajectory as it bounces and accelerates/decelerates around the game area, akin to asteroids and pool
- 'Power up' bee's speed and experience deceleration, akin to shuffleboard
- Start, pause, and reset the game, while maintaining the browser session's high score
- Experience delightful visual feedback based on the bee's position, speed, and direction
This project is implemented with the following technologies:
- The
Canvas API
to render the game board, withrequestAnimationFrame
and time delta correction behind the scenes to correct for play speed across devices Webpack
andBabel
to bundle and transpile the sourceJavaScript
codenpm
to manage project dependenciesCSS
andHTML
to generate the webpage around the game play area and the welcome screenKeymaster
to bind keys to methods
When the player flies the bee over flowers, pollen sparkles appear:
Pollen sparkles are extra delightful because their color and position is randomized, and they have a few frames of persistence, making them slightly lag in the bee's pollination wake.
Pollen sparkles are implemented as a Pollen
class, with each sparkle being an instance. Bee <> flower collision detection in the Game
class activates new pollen sparkle creation.
When each pollen sparkle is created, its position is set with a call to Pollen.pollenPos()
, which leverages a math helper method in a math utilities file:
pollenPos() {
let posCorrection = 0;
return Util.randomPosAroundCenterpoint(
this.game.bee.pos,
CONSTANTS.POLLEN_DIST,
posCorrection
);
}
The math helper function generates a random position radially around the given centerpoint using Math.cos() and Math.sin(), rather than 'squarely' using Math.random() on the centerpoint directly. This enables pollen sparkles to appear in a pleasant circular area around the bee.
export function randomPosAroundCenterpoint(centerpoint, distance, correction) {
let posRadius = distance * Math.sqrt(Math.random());
let angleInRad = Math.random() * 2 * Math.PI;
return [
centerpoint[0] + posRadius * Math.cos(angleInRad) + correction,
centerpoint[1] + posRadius * Math.sin(angleInRad) + correction,
];
}
The Pollen
class sets pollen sparkle color
by picking a random HEX code from an array during instance construction.
A math helper, Util.generateHexagonPoints(this.pollenPosition,Pollen.RADIUS)
, is called during instance construction to generate the hexagon point coordinates (points
) that shape the pollen sparkle (this is a bee themed game after all!).
The Pollen
class then draws pollen in 2 steps. 1: check that the pollen sparkle is 'younger' than a certain number of frames, else delete the pollen sparkle instance. 2: Draw the hexagon as a Canvas API
path shape using the pre-determined points
and color
.
drawPollen(ctx) {
if (this.pollenTimer > Pollen.PERSISTENCE) {
this.game.remove(this);
}
ctx.fillStyle = this.color;
ctx.beginPath();
this.points.forEach((pos) => {
ctx.lineTo(pos[0], pos[1]);
});
ctx.closePath();
ctx.fill();
this.pollenTimer += 1;
}
The game begins with the bee at the beehive, and the player must set a direction and launch speed - like in cannon shooters. Direction adjusts based on the player's keyboard inputs, and speed slides on its own and is 'locked in' based on player's timing of bee launch:
To set direction, the GameView
class listens to inputs from ASDW and arrow keys. It also checks if the bee is launched and which direction it is traveling in, and fires off different methods conditionally:
wKeyHandler() {
let nudgeDirection = {
up: "",
down: "",
left: "right",
right: "left",
};
let beeDirection = this.game.bee.beeDirection();
if (this.game.bee.launched) {
this.game.bee.nudge(nudgeDirection[beeDirection]);
} else if (!this.game.bee.launched) {
this.game.bee.setTrajectory(this.directions.UP);
}
}
If the bee is not yet launched, then Bee.setTrajectory(direction)
is activated, and nudges Bee.START_VEL
. This velocity is a placeholder whose value overrides bee's vel
once the bee is set in motion. Bee's vel
is what dictates actual bee position during gameplay.
setTrajectory(direction) {
let nudgeFactor = 0;
if (direction === this.directions.UP) {
nudgeFactor = CONSTANTS.NUDGE;
} else if (direction === this.directions.DOWN) {
nudgeFactor = -CONSTANTS.NUDGE;
}
const cosA = Math.cos(nudgeFactor);
const sinA = Math.sin(nudgeFactor);
const newX = Bee.START_VEL[0] * cosA + Bee.START_VEL[1] * sinA;
const newY = -Bee.START_VEL[0] * sinA + Bee.START_VEL[1] * cosA;
Bee.START_VEL = [newX, newY];
}
To draw the arrow representing the selected direction, Bee.drawTrajectory(ctx)
extrapolates the arrow tip from the current Bee position and the nudged Bee.START_VEL
value, and calls the math helper Util.calculateTriangleCoord(this.pos, pointerDirection)
to calculate where the 2 other triangle points are. This helper does this using the angle between the bee position and the arrow tip. This ensures the arrow moves in a circle and always points outward!
export function calculateTriangleCoord(originPos, pointerTipPos) {
const xDistance = Math.abs(originPos[0] - pointerTipPos[0]);
const yDistance = Math.abs(originPos[1] - pointerTipPos[1]);
const pointDistance = Math.sqrt(xDistance ** 2 + yDistance ** 2);
let knownPointsAngleDeg = (Math.atan(yDistance / xDistance) * 180) / Math.PI;
let inverseAngleDeg = 90 - knownPointsAngleDeg;
let desiredSharpnessDeg = 30;
let pointerLength = pointDistance * 0.2;
let newPoints = [];
[1, -1].forEach((factor) => {
let y =
pointerLength *
Math.cos(
((inverseAngleDeg + factor * desiredSharpnessDeg) * Math.PI) / 180
);
let x =
pointerLength *
Math.sin(
((inverseAngleDeg + factor * desiredSharpnessDeg) * Math.PI) / 180
);
let xAdj = originPos[0] <= pointerTipPos[0] ? x : -x;
let yAdj = originPos[1] <= pointerTipPos[1] ? -y : y;
newPoints.push([pointerTipPos[0] - xAdj, pointerTipPos[1] + yAdj]);
});
return newPoints;
}
Meanwhile the speed setter, Bee.slideScale()
, increments slideStep
up or down with each game step and updates the Bee's speed
value by that factor. The slider is then drawn as a rectangle of variable width by Bee.drawScale(ctx)
, based on the current speed
value.
When the player activates launch with the space bar, the current game step's Bee speed
and Bee.START_VEL
values are locked in. A math helper method calculates the final velocity, and the Bee vel
is updated to the resultant velocity value. Bee launches!
launch() {
this.vel = Util.scale(Bee.START_VEL, this.speed);
this.launched = true;
}
Each time the game steps forward, the Game
class checks if the bee is launched or not, and stops drawing the slider and arrow if the bee is no longer in cannon launch mode. This makes for a cleaner interface that shows these visuals only when they are relevant.
Take a look at the source files for implementation of other notable features:
- Bee acceleration and deceleration mechanics, including how bee speed triggers beehive sparkles and how top speed is capped. See
Bee
class. - requestAnimationFrame with time deltas for consistent game speed. See
GameView
andMovingObject
game classes. - Gameboard setup with randomized yet fenced positions for wasps, flowers, and speed strips. Note that flowers and speed strips spawn at positions that never overlap each other. See
Game
class. - Bounce mechanics. See
util.js
. - Bee and wasp sprite sheet parsing, both with traditional one-image sprite sheets and multi-file sprite frames. See
Bee
andWasp
classes. - Customized win/lose screens with changing messages based on game outcomes. See
GameView
class.
Upcoming improvements include:
- Add additional levels with different assortments of enemies and point-giving objects
- Add additional areas of altered physics, such as wormholes
-
Keymaster by madrobby
-
Witty content by ChatGPT
-
Color array by mucar
-
Character sprites by Robert Brooks and Olga Romero Lopez
-
Images:
- Background image by Freepik
- Background image by babysofja on Freepik
- Background image by Chen from Pixabay
- SpeedStrip image by starline on Freepik
- Flower graphic by DALL-E