In this post I will cover how to create a rain effect using the JavaScript game engine, Crafty.js, for a 2D side-scroller type of game. If you're new to Crafty, then feel free to check out my previous posts in which I cover the framework from a beginner's perspective.
And if you're familiar with Crafty already, then read on and enjoy.
Setting up Crafty.js
First things first, let's load up Crafty.js into our project. You can grab a fresh minified version of Crafty.js right here. The file has a small footprint and is cross browser compatible, so I recommend you load it directly from your local dev machine instead of from the interwebs.
<script type="text/javascript" src="crafty.js"></script>
Initialize Crafty.js
Now let's run some initialization code. Every Crafty.js project runs the following method call on startup in order to be instantiated. This will set the canvas width and height and the DOM element into which we'll be loading our game. The DOM element is an optional parameter, and Crafty will create one by default if one isn't specified, which is convenient.
<script>
var screenWidth = 800;
var screenHeight = 400;
Crafty.init(screenWidth,screenHeight, document.getElementById('game'));
</script>
Let's get started
This example will feature a simple blank canvas with not much in it besides a slightly dark colored background and some rain particles. I'll also generate a "Ground" element, just to have a marker in place to make our rain droplets dissipate.
Create the Floor
Everything in Crafty.js is an Entity essentially. And entities have Components associated to them. More on that in my beginner's tutorials.
We can't have rain without the ground for it to splash upon. So let's set up a ground element onto our canvas.
Crafty.e('Floor, 2D, Canvas, Solid, Color')
.attr({x: 0, y: 350, w: screenWidth * 2, h: 10})
.color('black');
This will create a block element in memory. In order to render the entity onto the page Crafty requires you set the attr values for the dimensions and to also set the color component as well.
Notice the 'Floor' component which I've added to the element. This will be used as a reference identifier later on when we're associating other elements to this one.
A Little On Gravity
A few words on Gravity before I continue. We can give any entity Gravity by assigning the 'Gravity' Component to its declaration. Doing that will make the entity fall forever essentially. We can, however, specify whether it should fall until it reaches another element, which we will. In this example, we'll have each drop fall until it reaches the 'Floor' component which I defined above.
Crafty.e('RainDrop, 2D, Canvas, Color, Solid, Gravity')
.attr({x: 0, y: 0, w: 2, h:15})
.color('black')
.gravity('Floor')
.gravityConst(.2);
Also notice the gravityConst method which will specify how much gravity to apply. You'll have to play around with this number to get your desired effect. And that is enough to have the full effect of gravity on that tiny raindrop. Next up, let's put it all together.
Making it rain
And now the hero in our scene, the raindrop. Raindrops can come in a myriad of shapes, sizes, colors and patterns. For this example, I will focus on a basic raindrop falling at a slight angle and then vanish once it makes contact with the ground element. The following function will render a single raindrop when called.
function drop()
{
var random_x = Math.floor((Math.random() * screenWidth) + 50);
Crafty.e('Drop, 2D, Canvas, Color, Solid, Gravity')
.attr({x: random_x, y: 0, w:2, h: 15})
.color('#000080')
.gravity('Floor')
.gravityConst(.2);
}
- A few things to notice. I'm setting the 'x' coordinate of the raindrop to some random location on the canvas, based on the width of the playing field.
- Secondly, our rain drop is just another Entity object with a specific width/height and the Gravity component set.
- Thirdly, this will generate a single raindrop, so we'll have to call this function numerous time per second in order to get the desired effect. Which brings up my next point.
Entering the Frame
By default Crafty runs 50 frames per second and we can make use of that because each time it enters a frame it calls the EnterFrame method.
Crafty.bind("EnterFrame", function(){
drop();
});
A convenient trick in order to reduce speeds on elements is to use the frame property that crafty provides in order mod with any integer, as such.
Crafty.bind("EnterFrame", function(){
if (Crafty.frame() % 2 == 0)
drop();
});
This will run the drop function 25 times per second, which is just fast enough to produce a "realistic" rain effect.
Destroying the drops
If we don't destroy the elements when we're done with them, then we're quickly going to shut down our machines, as I learned the hard way on a cold rainy digital day.
In order to do this, we'll have to bind our drop entities to the EnterFrame method and on each frame check it's position. Once it's y value is greater than the game boards, we can call destroy on it.
function drop()
{
var randomx = Math.floor((Math.random() * screenWidth) + 50);
Crafty.e('Drop, 2D, Canvas, Color, Solid, Gravity')
.attr({x: randomx, y: 0, w: 2, h: 15})
.color('#000080')
.gravity()
.gravityConst(.2)
.bind("EnterFrame", function() {
if (this.y > screenHeight)
this.destroy();
});
}
In Action
Here's a running example for your rain viewing pleasure.
View Source
<html>
<head>
<style>
#game
{
border:solid 1px black;
border-radius:8px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="game"></div>
<script type="text/javascript" src="https://rawgithub.com/craftyjs/Crafty/release/dist/crafty-min.js"></script>
<script>
var screenWidth = 800;
var screenHeight = 400;
Crafty.init(screenWidth,screenHeight, document.getElementById('game'));
Crafty.e('Floor, 2D, Canvas, Solid, Color')
.attr({x: 0, y: 350, w: screenWidth * 2, h: 10})
.color('black');
function drop()
{
var randomx = Math.floor((Math.random() * screenWidth) + 50);
Crafty.e('Drop, 2D, Canvas, Color, Solid, Gravity, Collision')
.attr({x: randomx, y: 0, w: 2, h: 15})
.color('#000080')
.gravity('Floor')
.bind("EnterFrame", function() {
if (this.y > 330)
this.destroy();
});
}
Crafty.bind("EnterFrame", function(){
if (Crafty.frame() % 2 == 0)
drop();
});
</script>
</body>
</html>