Advertisement
Visual effects in games define their
overall look and feel, and gameplay. Players are attracted to high
visual quality, which generate more traffic and reach. It’s key for
creating successful games and providing a lot of fun for players.
In this article I want to present a few ideas of how to implement different visual effects in
<canvas>
-based HTML5 games. These examples will be based on effects we made in our game, Skytte1. I will explain the basic ideas supporting them and provide the effects used in our work.What You Will Learn Link
Before we get going, I want to set out the things I hope you’ll learn from this article:-
Basic game design
We’ll look at patterns that are commonly used to make games and game effects like: game loops, sprites, collisions, and particle systems. -
Basic implementation of visual effects
We will also explore the theory and some code examples supporting these patterns.
Common Patterns Link
Let’s start with some common patterns and elements used in game development.
Sprites Link
These are simply 2-D images that represent an object in the
game. Sprites can be used for static objects, but also animated objects,
when each sprite represents a frame of sequential animation. They can
also be used for making user interface elements.
Usually games contain between dozens and hundreds of sprites. To
reduce memory usage and the processing power needed to handle these
images, many games use sprite sheets.Sprite Sheets Link
These are used to group a set of single sprites in one image. This reduces the amount of files in the game, resulting in reduced memory and processing power usage. Sprite sheets contain many single sprites stacked next to each other in rows and columns, and like the sprites they contain can be used statically or for animation.Here’s an article from Code + Web3 to help you better understand the benefits of using sprite sheets.
Game Loops Link
It’s important to realize that game objects don’t really
move on screen. An illusion of movement is achieved by rendering a
snapshot of a game world to the screen, advancing the game time a small
amount (usually 1/60th of a second), and then rendering things again.
This is literally a stop-motion effect and is used in both 2-D and 3-D
games. A game loop is a mechanism that implements this stop-motion.
It’s the main component needed to run a game. It runs continuously over
time, performing various tasks. On each iteration it processes user
input, moves entities, checks for collisions, and renders the game
(preferably in this order). It also controls game time that’s elapsed
between frames.
Below is a very basic game loop in JavaScript:var lastUpdate;
function tick() {
var now = window.Date.now();
if (lastUpdate) {
var elapsed = (now-lastUpdate) / 1000;
lastUpdate = now;
// Update all game objects here.
update(elapsed);
// ...and render them somehow.
render();
} else {
// Skip first frame, so elapsed is not 0.
lastUpdate = now;
}
// This makes the `tick` function run 60 frames per second (or slower, depends on monitor's refresh rate).
window.requestAnimationFrame(tick);
};
Note that the above example is very simplistic. It uses variable delta time (the elapsed
variable) and it’s recommended to upgrade this code to use fixed delta time. See this article for more details4.Collision Detection Link
Collision detection refers to finding the intersections
between objects. This is essential for many games because it’s used to
detect if a player hits a wall or a bullet hits an enemy, and so on.
When a collision is detected it can be used for game logic; for example,
when a bullet hits the player, the health score is reduced by 10
points.
There are a lot of collision detection algorithms, and
because it’s a performance-heavy operation, it’s important to choose the
best method wisely. To read more about collision detection, algorithms
and how they can be implemented, here’s an article from MDN5.
Particles And Particle Systems Link
Particles are basically sprites used by a particle system.
In game development, a particle system is a component that consists of a
particle emitter and particles assigned to that emitter. It’s used to
simulate various effects, like fire, explosions, smoke, and rain
effects. Particles are emitted over time and each emitter has its own
parameters to define various variables used for the simulated effect,
such as velocity, color, a particle’s lifetime or duration, gravity,
friction, and wind speed.
Euler Integration Link
Euler integration is a method for numerically integrating
the equations of motion. Each object’s position is calculated based on
its velocity, mass and force, and needs to be recalculated for each tick
in the game loop. The Euler method is the most basic and useful for
games like side-scrolling shooters, but there are also other methods
like Verlet integration and RK4 integration which are better for other
tasks. Below I’ll show a simple implementation of the idea.
You need a basic structure to hold an object’s position,
velocity and other movement-related data. We propose two identical
structures, but each with different meaning in the world’s space: point
and vector. Usually game engines use some kind of vector class, but the
distinction between points and vectors is very important and greatly
improves code readability (for example, you calculate distance not
between two vectors, but two points, which is more natural).
Point Link
Simply, it represents an element in two-dimmensional space with x and y coordinates which define where the point is located in that space.
function point2(x, y) {
return {'x': x || 0, 'y': y || 0};
}
Vector Link
A vector is a geometric object that has length (or
magnitude) and direction. In 2-D games vectors are used mostly to
describe forces (e.g. gravity, air resistance and wind) and velocities,
as well as proscribing movements or how light reflects off an object.
Vectors have many uses.
function vector2(x, y) {
return {'x': x || 0, 'y': y || 0};
}
The above functions create new two-dimensional vectors and points. Internally we don’t use the
What follows are some very common functions used on the
two-dimensional structures defined above. First, calculating the
distance between two points:new
operator in this case in JavaScript to gain a lot of performance. Also
note that there are some third-party libraries available that you could
use to manipulate vectors (glMatrix6 is a good candidate for this).point2.distance = function(a, b) {
// The x and y variables hold a vector pointing from point b to point a.
var x = a.x - b.x;
var y = a.y - b.y;
// Now, distance between the points is just length (magnitude) of this vector, calculated like this:
return Math.sqrt(x*x + y*y);
};
The magnitude (length) of a vector can be calculated directly from the last line of the above function like this:vector2.length = function(vector) {
return Math.sqrt(vector.x*vector.x + vector.y*vector.y);
};
Normalization of vectors is also very handy. The function below
resizes a vector so it becomes a unit vector; that is, its length is 1,
but its direction is maintained.vector2.normalize = function(vector) {
var length = vector2.length(vector);
if (length > 0) {
return vector2(vector.x / length, vector.y / length);
} else {
// zero-length vectors cannot be normalized, as they do not have direction.
return vector2();
}
};
Another useful case is to have a unit vector whose direction is pointing from one location to another:
// Note that this function is different from `vector2.direction`.
// Please don't confuse them.
point2.direction = function(from, to) {
var x = to.x - from.x;
var y = to.y - from.y;
var length = Math.sqrt(x*x + y*y);
if (length > 0) {
return vector2(x / length, y / length);
} else {
// `from` and `to` are identical
return vector2();
}
};
The dot product is an operation on two vectors (usually unit
vectors), which returns a scalar number representing the relation
between angles of those vectors.vector2.dot = function(a, b) {
return a.x*b.x + a.y*b.y;
};
The dot product is a length of a vector a projected on a vector b. A returned value of 1 means that both vectors point in the same direction. A value of -1 means that vector a points in the opposite direction of vector b. A value of 0 means that vector a is perpendicular to vector b.
Here’s an example of an entity class, so other objects can inherit
from it. Only basic properties related to movement are described.function Entity() {
...
// Center of mass usually.
this.position = point2();
// Linear velocity.
// There is also something like angular velocity, not described here.
this.velocity = vector2();
// Acceleration could also be named `force`, like in the Box2D engine.
this.acceleration = vector2();
this.mass = 1;
...
}
You can use pixels or meters as the unit in your game. We encourage
you to use meters, as it’s easier to balance things out during
development. Velocity, then, should be meters per second, and
acceleration should be meters per second squared.When using a third-party physics engine, you just store a reference to a physical body (or set of bodies) in your entity class. Then, the physics engine stores the mentioned properties, like position and velocity, inside each body for you.
Basic Euler integration looks like this:
acceleration = force / mass
velocity += acceleration
position += velocity
The code above must be executed in each frame for each
object in the game. Here is a basic implementation of the above in
JavaScript:
Entity.prototype.update = function(elapsed) {
// Acceleration is usually 0 and is set from the outside.
// Velocity is an amount of movement (meters or pixels) per second.
this.velocity.x += this.acceleration.x * elapsed;
this.velocity.y += this.acceleration.y * elapsed;
this.position.x += this.velocity.x * elapsed;
this.position.y += this.velocity.y * elapsed;
...
this.acceleration.x = this.acceleration.y = 0;
}
elapsed
is the amount of time in seconds that has passed
since the last frame (since the last call to this method). For games
running at 60 frames per second, the elapsed
value is usually 1/60 of a second, which is 0.016(6)s.The article on delta time mentioned earlier also covers this problem11.
To move objects, you can change their acceleration or velocity. Two functions shown below should be used for this purpose:
Entity.prototype.applyForce = function(force, scale) {
if (typeof scale === 'undefined') {
scale = 1;
}
this.acceleration.x += force.x * scale / this.mass;
this.acceleration.y += force.y * scale / this.mass;
};
Entity.prototype.applyImpulse = function(impulse, scale) {
if (typeof scale === 'undefined') {
scale = 1;
}
this.velocity.x += impulse.x * scale / this.mass;
this.velocity.y += impulse.y * scale / this.mass;
};
To move an object to the right you could do this:// 10 meters per second in the right direction (x=10, y=0).
var right = vector2(10, 0);
if (keys.left.isDown)
// The -1 inverts a vector, i.e. the vector will point in the opposite direction,
// but maintain magnitude (length).
spaceShip.applyImpulse(right, -1);
if (keys.right.isDown)
spaceShip.applyImpulse(right, 1);
Note that objects set in motion stay in motion. You need to implement some kind of deceleration to stop a moving object (air drag or friction, maybe).Weapon Effects Link
Now I’ll explain how certain weapon effects are made in our HTML5 game, Skytte12.Plasma Link
This is the most basic weapon in our game, just one shot
each time. There are no special algorithms used for this weapon. When a
plasma bullet is fired the game simply draws a single sprite that’s
rotated over time.
A simple plasma bullet can be spawned like this:// PlasmaProjectile inherits from Entity class
var plasma = new PlasmaProjectile();
// Move right (assuming that X axis is pointing right).
var direction = vector2(1, 0);
// 20 meters per second.
plasma.applyImpulse(direction, 20);
Blaster Link
This weapon is a little more complex. It also draws simple
sprites as bullets but there’s some code that spreads them out a little
and applies a random speed. This gives a more devastating feeling to
this weapon, so players feel they can inflict more damage than with the
plasma weapon and have better crowd control when among enemies.
The code works similarly to the plasma weapon code, but it spawns three bullets and each has a slightly different direction.
// BlaserProjectile inherits from Entity class
var topBullet = new BlasterProjectile(); // This bullet will move slightly up.
var middleBullet = new BlasterProjectile(); // This bullet will move horizontally.
var bottomBullet = new BlasterProjectile(); // This bullet will move slightly down.
var direction;
// Angle 0 is pointing directly to the right.
// We start with the bullet moving slightly upwards.
direction = vector2.direction(radians(-5)); // Convert angle to an unit vector
topBullet.applyImpulse(direction, 30);
direction = vector2.direction(radians(0));
middleBullet.applyImpulse(direction, 30);
direction = vector2.direction(radians(5));
middleBullet.applyImpulse(direction, 30);
Some math functions are needed for the above code to work:function radians(angle) {
return angle * Math.PI / 180;
}
// Note that this function is different from `point2.direction`.
// Please don't confuse them.
vector2.direction = function(angle) {
/*
* Converts an angle in radians to a unit vector. Angle of 0 gives vector x=1, y=0.
*/
var x = Math.cos(angle);
var y = Math.sin(angle);
return vector2(x, y);
};
Ray Link
This one’s interesting. The weapon shoots a laser ray but
it’s procedurally generated in each frame (this will be explained
later). To detect hits, it creates a rectangular collider that deals
damage every second for as long as it collides with the enemy.
Rockets Link
This weapon shoots guided missiles. The rocket is a sprite
with a particle emitter attached to its end. There’s also some more
sophisticated logic, like searching for the nearest enemy or limiting
the turn value for a rocket to give it less maneuverability. Also,
rockets don’t start to seek for enemy targets immediately – they fly
straight for a time to avoid unrealistic behavior.
Rockets in Skytte move toward their nearest neighbor. This is
achieved by calculating the proper force needed for the projectile to
move in any given direction. To avoid moving only in straight lines the
force calculated shouldn’t be too big.
Assuming that
Rocket
is a class inheriting from the Entity
class described earlier.Rocket.prototype.update = function(elapsed) {
var direction;
if (this.target) {
// Assuming that `this.target` points to the nearest enemy ship.
direction = point2.direction(this.position, this.target.position);
} else {
// No target, so fly ahead.
// This will fail for objects that are still, so remember to apply some initial velocity when spawning rockets.
direction = vector2.normalize(this.velocity);
}
// You can use any number here, depends on the speed of the rocket, target and units used.
this.applyForce(direction, 10);
// Simple inheritance here, calling parent's `update()`, so rocket actually moves.
Entity.prototype.update.apply(this, arguments);
};
Flak Link
Flak was designed to shoot many small bullets (something like a shotgun), which are little dot sprites. It has some specific logic to randomly generate the position of these dots within a cone-shaped area.To generate random points in a cone-shaped area:
// Firstly get random angle in degrees in the allowed span. Note that the span below always points to the right.
var angle = radians(random.uniform(-40, 40));
// Now get how far from the barrel the projectile should spawn.
var distance = random.uniform(5, 150);
// Join angle and distance to create an offset from the gun's barrel.
var direction = vector2.direction(angle);
var offset = vector2(direction.x * distance, direction.y * distance);
// Now calculate absolute position in the game world (you need a position of the barrel for this purpose):
var position = point2.move(barrel, offset);
The random.uniform()
function returns a random floating point number between two values. A simple implementation could look like this:random.uniform = function(min, max) {
return min + (max-min) * Math.random();
};
Electro Link
Electro is fancy weapon that shoots lightning at enemies
within a particular radius. It has a limited range but can shoot at
several enemies at once and always hits successfully. It uses the same
algorithm to draw curved lines to simulate lightning as the ray weapon
but with a higher curve factor.
Techniques Used Link
Procedural Curved Line Link
To create the laser beam effect and electro weapon we’ve
developed an algorithm to count and transform the linear distance
between the player’s ship and the enemy. In other words, we measured the
distance between two objects, found the middle point and moved it
randomly alongside the section. We repeat this action for every new
section created.
To draw these sections we use HTML5 <canvas>
draw function lineTo()
. To achieve the glowing color we used multiple lines drawn over one another with more opaque color and a higher stroke width.To find and offset a point between two other points:
var offset, midpoint;
midpoint = point2.midpoint(A, B);
// Calculate an unit-length vector pointing from A to B.
offset = point2.direction(A, B);
// Rotate this vector 90 degrees clockwise.
offset = vector2.perpendicular(offset);
// We want our offset to work in two directions perpendicular to the segment AB: up and down.
if (random.sign() === -1) {
// Rotate offset by 180 degrees.
offset.x = -offset.x;
offset.y = -offset.y;
}
// Move the midpoint by an offset.
var offsetLength = Math.random() * 10; // Offset by 10 pixels for example.
midpoint.x += offset.x * offsetLength;
midpoint.y += offset.y * offsetLength;
Below are functions used in the above code:
point2.midpoint = function(a, b) {
var x = (a.x+b.x) / 2;
var y = (a.y+b.y) / 2;
return point2(x, y);
};
vector2.perpendicular = function(v) {
/*
* Rotates a vector by 90 degrees clockwise.
*/
return vector2(-v.y, v.x);
};
random.sign = function() {
return Math.random() < 0.5 ? -1 : 1;
};
Finding Nearest Neighbor Link
To find the nearest enemy for a rocket and the electro weapon, we iterate over an array of active enemies and compare their positions with the position of a rocket, or the shooting point in the case of the electro weapon. When a rocket locks on its target, it flies toward it until it hits or flies off the screen. For the electro weapon, it waits for a target to be in range.A basic implementation may look like this:
function nearest(position, entities) {
/*
* Given position and an array of entites, this function finds which entity is closest
* to `position` and distance.
*/
var distance, nearest = null, nearestDistance = Infinity;
for (var i = 0; i < entities.length; i++) {
// Allow list of entities to contain the compared entity and ignore it silently.
if (position !== entities[i].position) {
// Calculate distance between two points, usually centers of mass of each entity.
distance = point2.distance(position, entities[i].position);
if (distance < nearestDistance) {
nearestDistance = distance;
nearest = entities[i];
}
}
}
// Return the closest entity and distance to it, as it may come handy in some situations.
return {'entity': nearest, 'distance': nearestDistance};
}
Conclusion Link
These topics cover only the basic ideas that support them. I
hope after reading the article you now have a better idea of how to
start developing such things. Check out the resources below and try
doing something similar yourself.
Resources Link
(rb, ml, og)
Kamil Kaniuk
Kamil is the creative mind behind Merix Studio. He builds functional and good-looking websites, applications and HTML5 games. He loves open source, too.
Post a Comment