npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shaku

v2.2.4

Published

A simple and effective JavaScript game development framework that knows its place!

Downloads

196

Readme

Shaku JS

A simple and effective JavaScript game development framework that knows its place!

If you're looking for a package that implements rendering, sounds, assets and input, while keeping it low level and simple, this is the library for you!

(if you're looking for a game engine with editor like Unity, this library is not what you're looking for)

Demos & docs:

Projects made with Shaku:

(Want your game listed above? Contact me at [email protected])

Table Of Content

About

Shaku is a JavaScript framework for web games development that emphasize simplicity, flexibility and freedom.

It's pretty low level and designed to be used as the foundations for a higher-level game engine, or used directly for game development. Kind of like MonoGame, RayLib or libGDX.

Let's take a quick look at how we make a game main loop with Shaku:

// Init code goes here, we'll review it later..

// main loop (do updates, render and request next step)
function step() 
{  
    // start a new frame and clear screen
    Shaku.startFrame();
    Shaku.gfx.clear(Shaku.utils.Color.cornflowerblue);

    // draw a sprite using the spritebatch
    spritesBatch.begin();
    let position = new Shaku.utils.Vector2(400, 300);
    let size = new Shaku.utils.Vector2(100, 100);
    spritesBatch.drawQuad(texture, position, size);
    spritesBatch.end();


    // end frame and request next step
    Shaku.endFrame();
    Shaku.requestAnimationFrame(step);
}

Main Features

Shaku provides the following key features:

  • Ultra-fast WebGL rendering engine.
  • Assets loader to fetch textures, sounds, music, JSON, and other resources.
  • Texture Atlas builder to combine textures efficiently at runtime.
  • Collision detection.
  • Custom effects, text rendering, render targets, batching and other graphics-essentials.
  • Input manager for simple touch, mouse, gamepad and keyboard state-based input (instead of events).
  • Sound effects, music, tracks mixer, pitch and everything you need for sfx.
  • Basic utilities such as Vectors, Matrices, 2D Shapes, GameTime and more.
  • Advance utilities such as Animators, Path Finder, Perlin generator and other super useful stuff.
  • All packed in a tiny lib with no external dependencies! A single minified 150Kb JS is all you need to use Shaku.*

If you want to experiment with Shaku while reading the docs, you can check out this Sandbox Demo. See the demos assets folder to see which assets you can use for the sandbox (or load assets from external sources).

Sandbox

Installation

Using Shaku is super easy!

  1. Get shaku.js or shaku.min.js from the dist/ folder and include it in your page (or use npm to get it).
  2. Init Shaku and append the canvas to your document (or init Shaku on an existing canvas).
  3. Write a game loop method starting with Shaku.startFrame() and ending with Shaku.endFrame() and requestAnimationFrame() to get next frame.

To get Shaku via NPM:

npm install shaku

HTML Boilerplate

The following is a boilerplate HTML with Shaku running an empty game main loop:

<!DOCTYPE html>
<html>
  <head>
    <title>Shaku Example</title>
    <script src="dist/shaku.js"></script>
  </head>
  <body>
      <script>
        (async function runGame()
        {
          // init shaku
          await Shaku.init();

          // add shaku's canvas to document and set resolution to 800x600
          document.body.appendChild(Shaku.gfx.canvas);
          Shaku.gfx.setResolution(800, 600, true);

          // TODO: LOAD ASSETS AND INIT GAME LOGIC HERE

          // do a single main loop step and request next step inside
          function step() 
          {  
            // start a new frame and clear screen
            Shaku.startFrame();
            Shaku.gfx.clear(Shaku.utils.Color.cornflowerblue);

            // TODO: PUT YOUR GAME UPDATES AND RENDERING HERE

            // end frame and invoke the next step in 60 FPS rate (or more, depend on machine and browser)
            Shaku.endFrame();
            Shaku.requestAnimationFrame(step);
          }

          // start the main loop
          step();
        })();
      </script>
  </body>
</html>

You can find the above HTML file here.

Online Demo Projects

Online demo projects can be found here. They demonstrate basic to advanced Shaku features.

Demo-2

Using Shaku

Shaku's API mostly consist of five main managers, each solve a different domain of problems in gamedev: graphics, sounds, assets, collision and input.

In this doc we'll explore these managers and cover the most common use cases with them. If you want to dive deep into the API, you can check out the API Docs, or browse the code.

Setup

Everything in Shaku is located under the Shaku object.

Since the initialization process and asset loading is mostly asynchronous operations, its best to wrap the init code in an async method and utilize await calls to simplify the code. A common Shaku initialization code will look something like this:

(async function runGame()
{
    // init shaku.
    // for pixel art games its best to set antialias=false before init.
    Shaku.gfx.setContextAttributes({antialias: false});
    await Shaku.init();

    // add shaku's canvas to document and set resolution to 800x600.
    // this will set the canvas and renderer size.
    document.body.appendChild(Shaku.gfx.canvas);
    Shaku.gfx.setResolution(800, 600, true);

    // TODO: add code to load assets and init game logic here.

    // game main loop
    function step() 
    {  
        // start frame and clear the screen
        Shaku.startFrame();
        Shaku.gfx.clear(Shaku.utils.Color.cornflowerblue);

        // TODO: add game logic code here

        Shaku.endFrame();
        Shaku.requestAnimationFrame(step);
    }
    step();
})();

Let's go over the code above line by line:

  • Shaku.gfx.setContextAttributes({antialias: false}) will disable smooth filtering, and keep everything crispy and pixelated.
  • await Shaku.init() will initialize all Shaku's managers.
  • document.body.appendChild(Shaku.gfx.canvas) add the canvas created by Shaku to the document body (you can also use an existing canvas instead).
  • Shaku.gfx.setResolution(800, 600, true) will set both canvas size and renderer size to 800x600 px.
  • Shaku.startFrame() must be called at the beginning of every game frame.
  • Shaku.gfx.clear(Shaku.utils.Color.cornflowerblue) will clear the canvas to blue-ish color.
  • Shaku.endFrame() must be called at the end of every game frame.
  • Shaku.requestAnimationFrame(step) will request next frame when its time to render screen again, keeping updates() rate at about 60 FPS (or more if the browser and machine allows it).

As you can see from the example above, our step() method represent a single iteration in our game main loop. It handles both rendering and updates.

Now we can start using Shaku's managers, mostly between the startFrame() and endFrame() calls.

Graphics

Let's start exploring the APIs from the graphics manager, accessed by Shaku.gfx.

In Shaku we use batches to render everything. These batches collect multiple draw calls and batch them together into a single GPU call. This way of rendering is essential for performance, but it has some limitations. For example, you can only only batch rendering with the same texture, blend mode, and shaders.

This doc don't cover the entirely of the API, only the main parts of it. To see the full API, check out the API docs.

Drawing Textures

To draw textures (also known as 2d sprites) we use a SpriteBatch renderer. This renderer batch together 2d quads and other shapes with textures on them.

Let's see a minimal code example to render a texture on screen:

// this part comes after we init shaku, but still outside the main loop:

// during the init phase, we create a spritebatch and load a texture to draw
const texture = await Shaku.assets.loadTexture('<your texture path here..>');
const spriteBatch = new Shaku.gfx.SpriteBatch();

// this part comes between the Shaku.startFrame() and the Shaku.endFrame() calls:

// draw the texture with the batch
spritesBatch.begin();
let position = new Shaku.utils.Vector2(400, 300);
let size = new Shaku.utils.Vector2(100, 100);
spritesBatch.drawQuad(texture, position, size);
spritesBatch.end();

Pretty simple, eh? Now let's draw with some more parameters:

spritesBatch.begin();
let position = new Shaku.utils.Vector2(100, 125);
let size = new Shaku.utils.Vector2(32, 64);
let sourceRect = new Shaku.utils.Rectangle(32, 0, 32, 64);
let color = Shaku.utils.Color.red;
let rotation = Math.PI / 2;
let origin = new Shaku.utils.Vector2(0.5, 1);
let skew = new Shaku.utils.Vector2(32, 0);
Shaku.gfx.drawQuad(texture, position, size, sourceRect, color, rotation, origin, skew);
spritesBatch.end();

When beginning a batch, you can set different blend modes and effects. For example:

spritesBatch.begin(Shaku.gfx.BlendModes.Additive, myCustomEffect);

We'll learn more about effects later, don't worry about it for now.

A simple rendering demo can be found here.

When does a GPU draw call is made?

The SpriteBatch will call the GPU to draw everything on three different occasions:

  1. When spritesBatch.end() is called.
  2. When you change the texture.
  3. If the batch overflows and can't contain any more drawings.

As you can see number #2 is something we need to watch out for. Texture Atlases are great way to reduce draw calls, and when possible, you should sort your rendering order by textures. We'll learn more about Texture Atlases later.

To learn more about Sprite Batches and see what else you can do with them, its recommended to check out the docs. For example you can control pixel aligning, buffers size, how to handle overflow, etc.

Sprites

Sprites are entities that store all rendering parameters required to make a draw call. It's just a more object-based approach to draw stuff.

Lets create a sprite and set some of its fields:

// this part comes after we init shaku, but still outside the main loop:

// load texture and create sprite
let texture = await Shaku.assets.loadTexture('assets/my_texture.png');
let sprite = new Shaku.gfx.Sprite(texture);

// set some fields
sprite.position.set(100, 125);
sprite.size.set(32, 64);
sprite.sourceRectangle = new Shaku.utils.Rectangle(32, 0, 32, 64);
sprite.color = Shaku.utils.Color.red;
sprite.rotation = Math.PI / 2;
sprite.origin.set(0.5, 1);

// this part comes between the Shaku.startFrame() and the Shaku.endFrame() calls:

// draw the sprite in a sprite batch
spritesBatch.begin();
Shaku.gfx.drawSprite(sprite);
spritesBatch.end();

Sprites Group

As the name implies, a sprites group is a collection of sprites. Let's see how we use it:

// this part comes after we init shaku, but still outside the main loop:

// create a group
let group = new Shaku.gfx.SpritesGroup();

// set group position, scale and rotation
// these transformations will affect all sprites in group
group.position.set(100, 100);
group.rotation = Math.PI / 2;
group.scale.set(2, 2);

// add some sprites to the group
let texture = await Shaku.assets.loadTexture('assets/my_texture.png');
for (let i = 0; i < 3; ++i) {
  let sprite = new Shaku.gfx.Sprite(texture);
  sprite.position = new Shaku.utils.Vector2(i * 100, 0);
  sprite.size = new Shaku.utils.Vector2(50, 50);
  sprite.origin = new Shaku.utils.Vector2(0, 0);
  group.add(sprite);
}

// this part comes between the Shaku.startFrame() and the Shaku.endFrame() calls:

// draw group
spritesBatch.begin();
Shaku.gfx.drawSpriteGroup(group);
spritesBatch.end();

The advantage of groups is that you can apply common transformations on all the sprites in the group around the same origin point. Its also slightly more efficient in some cases.

A demo page that draw with sprites group can be found here.

Drawing 3D Sprites

Shaku provides a simple 3D Sprite Batch renderer. This is useful for simple 3D stuff like this:

3D Sprites

Let's take a look at a basic 3D sprites example:

// this part comes after we init shaku, but still outside the main loop:

// create a 3d sprite and set a default perspective camera.
// check out setPerspectiveCamera() arguments to see more options.
let spritesBatch3d = new Shaku.gfx.SpriteBatch3D();
spritesBatch3d.setPerspectiveCamera();

// this part comes between the Shaku.startFrame() and the Shaku.endFrame() calls:

// begin drawing 3d sprites
spritesBatch3d.begin();

// set view matrix (camera position and where we look at)
spritesBatch3d.setViewLookat(
  new Shaku.utils.Vector3(0, 500, 600), 
  new Shaku.utils.Vector3(0, 0, 0)
);

// draw 3d quad from 4 vertices
const v1 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(-spriteSize.x / 2, 0, 0))
          .setTextureCoords(new Shaku.utils.Vector2(0, 1));
const v2 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(spriteSize.x / 2, 0, 0))
          .setTextureCoords(new Shaku.utils.Vector2(1, 1));
const v3 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(-spriteSize.x / 2, spriteSize.y, 0))
          .setTextureCoords(new Shaku.utils.Vector2(0, 0));
const v4 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(spriteSize.x / 2, spriteSize.y, 0))
          .setTextureCoords(new Shaku.utils.Vector2(1, 0));
spritesBatch3d.drawVertices(texture, [v1, v2, v3, v4]);

// end rendering
spritesBatch3d.end();

Drawing Shapes

Shaku also provides a way to draw some basic 2D shapes:

// this part comes after we init shaku, but still outside the main loop:

// create shapes batch to render 2d shapes
let shapesBatch = new Shaku.gfx.ShapesBatch();

// this part comes between the Shaku.startFrame() and the Shaku.endFrame() calls:

// start drawing shapes
shapesBatch.begin();

// draw a circle in the center of screen with radius of 400. its center is red, its outter parts are blue, and it has 32 segments.
shapesBatch.drawCircle(new Shaku.utils.Circle(Shaku.gfx.getCanvasSize().div(2), 400), Shaku.utils.Color.red, 32, Shaku.utils.Color.blue);

// draw a rectangle at offset 100,100 and size 256,256, with red color, that rotates over time
let rotation = Shaku.gameTime.elapsed;
shapesBatch.drawRectangle(new Shaku.utils.Rectangle(100, 100, 256, 256), Shaku.utils.Color.red, rotation);

// draw everything on screen
shapesBatch.end();

Similar to ShapesBatch, there's also a LinesBatch renderer to draw just the outline of shapes (or a string of lines from vertices).

Drawing Text

Shaku support rendering text from Font Texture (also known as Bitmap Fonts). These fonts store the glyphs as pixels data, and render the text as sprites.

You don't need to prepare the Font Textures upfront; Shaku generates them at runtime from regular TTF fonts. For example, Shaku generated the following font texture for one of the online demos:

font texture

Let's take a look at how we generate a Sprite Font and render some text with it:

// this part comes after we init shaku, but still outside the main loop:

// load font texture
// note: the fontName argument MUST match the font name defined in the ttf file.
let fontTexture = await Shaku.assets.loadFontTexture('assets/DejaVuSansMono.ttf', {fontName: 'DejaVuSansMono'});

// create text sprite batch
let textSpriteBatch = new Shaku.gfx.TextSpriteBatch();

// generate a text group to render in white, aligned to the left, and positioned at 100,100.
let textGroup = Shaku.gfx.buildText(fontTexture, "Hello World!\nThis is second line.", 24, Shaku.utils.Color.white, Shaku.gfx.TextAlignments.Left);
textGroup.position.set(100, 100);

// this part comes between the Shaku.startFrame() and the Shaku.endFrame() calls:

// draw the text
textSpriteBatch.begin();
textSpriteBatch.drawText(textGroup);
textSpriteBatch.end();

// you can also draw the text with outlines:
textSpriteBatch.outlineWeight = 0.75;
textSpriteBatch.outlineColor = Shaku.utils.Color.black;
textSpriteBatch.begin();
textSpriteBatch.drawText(textGroup);
textSpriteBatch.end();

When loading the FontTexture you can provide additional parameters, to learn more about them check out the API Docs.

Note that Shaku also support hi-res MSDF Font Textures, but it can't generate them at runtime. To see how to use MSDF font textures, check out this demo.

Render Targets

Render Targets provide a way to draw on textures instead of directly on screen, and then draw these textures on screen. This technique is useful for post processing effects, or to implement virtual resolution (by drawing on a constant-sized texture and then present it on screen).

For example the following game is built with Shaku, and uses Render Targets to implement the 2D lightings you see here:

HellEscape

A render target in essence is just a texture asset you can draw on, created like this:

let renderTarget = await Shaku.assets.createRenderTarget('_my_render_target', width, height);

Then you can start drawing on it by setting it as the active render target:

// set render target
Shaku.gfx.setRenderTarget(renderTarget);

// draw some stuff here... 
// these renderings will appear on the texture instead of the canvas.

// reset render target so we can continue drawing on screen / canvas
Shaku.gfx.setRenderTarget(null);

And finally we can use the render target just like we would use any other texture:

spritesBatch.begin();
let position = new Shaku.utils.Vector2(400, 300);
let size = new Shaku.utils.Vector2(100, 100);
spritesBatch.drawQuad(renderTarget, position, size);
spritesBatch.end();

A demo that uses render targets can be found here.

Cameras

The camera object define two key properties:

  • Viewport: the region of the canvas we render on.
  • Projection: offset and scale of everything we draw on the canvas.

By default, Shaku will use a camera with no offset and scale of x1, and a viewport that covers the entire canvas. In other words, an identity camera that won't affect anything.

To change the default camera:

// create camera object
let camera = Shaku.gfx.createCamera();

// set offset and use the camera (call this before rendering)
camera.orthographicOffset(cameraOffset);
Shaku.gfx.applyCamera(camera);

And if later you want to reset camera back to default, you can call the following method:

Shaku.gfx.resetCamera();

For more details, check out the Camera object API in the docs.

A demo page that uses cameras can be found here.

Texture Atlas

Every time you change the texture, a draw call is made to the GPU. This means that if you render 100 different textures in a row you will suffer 100 GPU draw calls (and 100 textures switching), which is very ineffective in terms of performance.

To solve this issue video games often use a Texture Atlas, which is a single large texture containing multiple smaller textures. That way, we can reduce texture switching and draw calls.

Creating a Texture Atlas manually is a tedious work, and as you add more and more textures sometimes you end up with inconvenient 'holes' that makes the atlas less space-efficient. For that reason, Shaku has a built-in Atlas builder that helps you generate an efficient Texture Atlas at runtime:

// all source textures URLs
const sourceUrls = [
    'assets/stone_wall.png',
    'assets/grass.png',
    'assets/tree.png',
    ...
];

// create a texture atlas named 'my-texture-atlas'.
// you can also limit its dimensions if needed to.
let textureAtlas = await Shaku.assets.createTextureAtlas('my-texture-atlas', sourceUrls);

// then you can use the texture atlas like this:

// extract one of the textures
// textureInAtlas is an object with `texture` and `sourceRectangle`.
let textureInAtlas = textureAtlas.getTexture('assets/stone_wall.png');

// draw the texture at 100,100
let size = textureInAtlas.sourceRectangle.getSize();
spritesBatch.begin();
spritesBatch.drawQuad(textureInAtlas, new Shaku.utils.Vector2(100, 100), size);
spritesBatch.end();

Note that a texture atlas is not necessarily a single texture; Since there's a GPU limit for max textures size, the atlas may generate more than one texture. That's why when you call getTexture() the object don't return just source rectangle, but a texture asset as well.

Effects

Effects provide a way to change the shaders Shaku uses to draw textures and shapes.

When implementing an effect, you need to follow four steps:

  1. Write or use an existing vertex shader code.
  2. Write or use an existing fragment shader code.
  3. Define your shaders Uniforms.
  4. Define your shaders Attributes.

And optionally, you can instruct Shaku to use custom attributes and uniforms internally, so you won't need to explicitly set them. More on that later.

Lets write a simple custom effect and then review and explain the code:

// define a custom effect
class MyEffect extends Shaku.gfx.SpritesEffect
{
    /**
     * Override the fragment shader for our custom effect.
     */
    get fragmentCode()
    {
      const fragmentShader = `  
            #ifdef GL_ES
                precision highp float;
            #endif

            uniform sampler2D mainTexture;
            uniform float elapsedTime;

            varying vec2 v_texCoord;
            varying vec4 v_color;

            void main(void) {
                gl_FragColor = texture2D(mainTexture, v_texCoord) * v_color;
                gl_FragColor.r *= sin(v_texCoord.y * 10.0 + elapsedTime) + 0.1;
                gl_FragColor.g *= sin(1.8 + v_texCoord.y * 10.0 + elapsedTime) + 0.1;
                gl_FragColor.b *= sin(3.6 + v_texCoord.y * 10.0 + elapsedTime) + 0.1;
                gl_FragColor.rgb *= gl_FragColor.a;
            }
        `; 
        return fragmentShader;
    }

    /**
     * Override the uniform types dictionary to add our custom uniform type.
     */
    get uniformTypes()
    {
      let ret = super.uniformTypes;
      ret['elapsedTime'] = { type: Shaku.gfx.Effect.UniformTypes.Float };
      return ret;
    }
}

Before reading on, can you guess what this effect do?

This effect recieve elapsed time as a uniform (called 'elapsedTime') with type flot, and animate the texture colors based on the current time value. Since every component gets a different offset from the start, it will create a rainbow-like colors effect.

Custom Effects

A demo page with the above effect can be found here.

Now lets review the code.

  1. First, notice we're extending the Shaku.gfx.SpritesEffect class. This is the default effect Shaku uses for sprites, and by inheriting from it we can skip implementing the vertex shader and just use the default one. It also covers the basic attributes binding for vertices data. If you want to create a brand new effect that doesn't use anything from the built-in sprites effect, extend Shaku.gfx.Effect instead.
  2. Next we have get fragmentCode(), that returns the fragment shader code to compile for this effect. There is also a get fragmentCode() getter for the vertex shader code, but as mentioned earlier we relay on the default sprites vertex shader so we don't need to implement it.
  3. And finally, get uniformTypes() returns a dictionary with uniforms we want to set in the effect. Note that we do let ret = super.uniformTypes; to extend the base class uniforms so we won't miss out on any functionality from the basic effect.

Now we can start using this effect with our Sprites Batch:

// create the custom effect
let effect = new MyEffect();

// update effect elapsed time and render with it
// the setter `effect.uniforms.elapsedTime` exists because we defined it in `get uniformTypes()`
effect.uniforms.elapsedTime(Shaku.gameTime.elapsed);
spritesBatch.begin(undefined, effect);
spritesBatch.drawQuad(texture, new Shaku.utils.Vector2(100, 100), 400);
spritesBatch.end();

Default Effect

To learn more about effects, lets review the default built-in effect Shaku normally uses for sprites with vertex color:

// vertex shader code
const vertexShader = `
attribute vec3 position;
attribute vec2 uv;
attribute vec4 color;

uniform mat4 projection;
uniform mat4 world;

varying vec2 v_texCoord;
varying vec4 v_color;

void main(void) {
    gl_Position = projection * world * vec4(position, 1.0);
    gl_PointSize = 1.0;
    v_texCoord = uv;
    v_color = color;
}
    `;

// fragment shader code
const fragmentShader = `  
#ifdef GL_ES
    precision highp float;
#endif

uniform sampler2D mainTexture;

varying vec2 v_texCoord;
varying vec4 v_color;

void main(void) {
    gl_FragColor = texture2D(mainTexture, v_texCoord) * v_color;
    gl_FragColor.rgb *= gl_FragColor.a;
}
    `;

/**
 * Default basic effect to draw 2d sprites.
 */
class SpritesEffect extends Effect
{
    /** @inheritdoc */
    get vertexCode() 
    { 
        return vertexShader; 
    }

    /** @inheritdoc */
    get fragmentCode()
    { 
        return fragmentShader;
    }

    /** @inheritdoc */
    get uniformTypes()
    {
        return {
            [Effect.UniformBinds.MainTexture]: { type: Effect.UniformTypes.Texture, bind: Effect.UniformBinds.MainTexture },
            [Effect.UniformBinds.Projection]: { type: Effect.UniformTypes.Matrix, bind: Effect.UniformBinds.Projection },
            [Effect.UniformBinds.World]: { type: Effect.UniformTypes.Matrix, bind: Effect.UniformBinds.World },
            [Effect.UniformBinds.View]: { type: Effect.UniformTypes.Matrix, bind: Effect.UniformBinds.View }
        };
    }

    /** @inheritdoc */
    get attributeTypes()
    {
        return {
            [Effect.AttributeBinds.Position]: { size: 3, type: Effect.AttributeTypes.Float, normalize: false, bind: Effect.AttributeBinds.Position },
            [Effect.AttributeBinds.TextureCoords]: { size: 2, type: Effect.AttributeTypes.Float, normalize: false, bind: Effect.AttributeBinds.TextureCoords },
            [Effect.AttributeBinds.Colors]: { size: 4, type: Effect.AttributeTypes.Float, normalize: false, bind: Effect.AttributeBinds.Colors },
        };
    }
}

As you can see above we use attributes for vertices data (position, texture coords, and colors), and uniforms for texture and transformation matrices (projection, world, and view).

Binds

You may have noticed an additional parameter added to the attributes and uniforms: bind.

Binding is a way for us to tell Shaku how to handle attributes and uniforms we want to use for basic stuff, like vertices color or texture coords, so that Shaku can set these values internally when preparing the batch.

For example, when we added the bind Effect.AttributeBinds.Position to the attributes named [Effect.AttributeBinds.Position], we instructed Shaku to send the vertices position data into this attribute from our vertex shader, which is called position.

If you want to define an effect and call this attribute a different name, for example 'attr_vertexPos', you can just set it like this in your effect:

attr_vertexPos: { size: 3, type: Effect.AttributeTypes.Float, normalize: false, bind: Effect.AttributeBinds.Position },

The following binds are valid for uniform types:

  • MainTexture: bind uniform to be used as the main texture.
  • Color: bind uniform to be used as a main color.
  • Projection: bind uniform to be used as the projection matrix.
  • World: bind uniform to be used as the world matrix.
  • View: bind uniform to be used as the view matrix.
  • UvOffset: bind uniform to be used as UV offset.
  • UvScale: bind uniform to be used as UV scale.
  • OutlineWeight: bind uniform to be used as outline weight.
  • OutlineColor: bind uniform to be used as outline color.
  • UvNormalizationFactor: bind uniform to be used as factor to normalize uv values to be 0-1.
  • TextureWidth: bind uniform to be used as texture width in pixels.
  • TextureHeight: bind uniform to be used as texture height in pixels.

And the following binds are valid for attribute types:

  • Position: bind attribute to be used for vertices position array.
  • TextureCoords: bind attribute to be used for texture coords array.
  • Colors: bind attribute to be used for vertices colors array.

Anything else, you'll just need to set yourself.

Matrices

Matrices are used to transform vertices. They can express rotation, scale, translation, and camera view and projection.

You can access the Matrix class with Shaku.gfx.Matrix. Let's take a look at a basic example of using a matrix to move the position of 3d vertices:

// create vertices
const spriteSize = new Shaku.utils.Vector2(spriteTexture.width * 1.5, spriteTexture.height * 1.5);
const v1 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(-spriteSize.x / 2, 0, 0))
          .setTextureCoords(new Shaku.utils.Vector2(0, 1));
const v2 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(spriteSize.x / 2, 0, 0))
          .setTextureCoords(new Shaku.utils.Vector2(1, 1));
const v3 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(-spriteSize.x / 2, spriteSize.y, 0))
          .setTextureCoords(new Shaku.utils.Vector2(0, 0));
const v4 = (new Shaku.gfx.Vertex())
          .setPosition(new Shaku.utils.Vector3(spriteSize.x / 2, spriteSize.y, 0))
          .setTextureCoords(new Shaku.utils.Vector2(1, 0));

// create a translation matrix
const position = new Shaku.utils.Vector3(10, 100, 50);
const matrix = Shaku.gfx.Matrix.translate(position.x, position.y, position.z);

// transform the vertices with the matrix and draw them in a 3d batch
const vertices = [Shaku.gfx.Matrix.transformVertex(matrix, v1), 
                  Shaku.gfx.Matrix.transformVertex(matrix, v2), 
                  Shaku.gfx.Matrix.transformVertex(matrix, v3), 
                  Shaku.gfx.Matrix.transformVertex(matrix, v4)];
spritesBatch3d.begin();
spritesBatch3d.drawVertices(spriteTexture, vertices);
spritesBatch3d.end();

To learn more about matrices, check out the API docs.

Conclusion

In this chapter we learned about the Gfx manager and how to use it to create batches and render basic things. What we covered here is just the common use cases, to learn more about what you can draw with Shaku check out the online demos or the online docs.

Sounds

We finally reached our second manager, the sounds manager, which is accessed by Shaku.sfx.

This doc don't cover the entirely of the API, only the main parts of it. To see the full API, check out the API docs.

Play Sound

To play an audio asset once:

// load the sound file
let sound = await Shaku.assets.loadSound('assets/my_sound_file.ogg');

// play sound
let volume = 0.85;
let pitch = 1;
Shaku.sfx.play(sound, volume, pitch);

Note that due to browsers security limitations, you won't be able to play any sound effect until the user interacted with the page with mouse, touch, gamepad, or keyboard. This is not something Shaku can solve, as this is by design and enforced by the browsers themselves.

You can, however, preload your sound assets before that.

Sound Instances

A sound instance is a way to create a sound object that lives on after it was played, and use it as many times as you like. Its more efficient to reuse instances than to call play() multiple times, but its usually an unnecessary optimization as this will rarely be your bottleneck in terms of performance.

To create a sound instance:

// load sound asset and create instance
let sound = await Shaku.assets.loadSound('assets/my_sound_file.ogg');
let soundInstance = Shaku.sfx.createSound(sound);

// play the sound
soundInstance.play();

The sound instance have the following properties:

  • play(): Start playing the sound.
  • stop(): Stop playing the sound and reset time to the beginning.
  • pause(): Stop playing sound but keep current time.
  • loop: Set if should play in loop or not.
  • volume: Set volume.
  • currentTime: Set current time.
  • duration: Get sound duration.
  • paused: Is the sound currently paused?
  • playing: Is the sound currently playing?
  • finished: Did the sound finish playing?
  • preservesPitch: Set if to preserve pitch while changing playback rate.
  • playbackRate: Set playback rate.

A demo page that play sounds can be found here.

Sounds Mixer

Sound Mixer is a utility class to mix and fade sounds. It's very useful for music transitioning.

For example, the following code will create a mixer that transition from music1 to music2, while allowing tracks to overlap (if overlap is false, the mixer will first fade-out music1 completely, and only then begin fading-in music2):

// load two music tracks
let music1 = await Shaku.assets.loadSound('assets/music1.ogg');
let music2 = await Shaku.assets.loadSound('assets/music2.ogg');

// transition between the tracks
let overlap = true;
let mixer = new Shaku.sfx.SoundMixer(Shaku.sfx.createSound(music1), Shaku.sfx.createSound(music2), overlap);

To run the mixer you then need to call the following inside every step of your game main loop:

// Shaku.gameTime.delta is transition speed (delta = time between frames, in seconds)
mixer.updateDelta(Shaku.gameTime.delta);

And the following code will create a mixer to fade music in without fading anything out:

let mixer = new Shaku.sfx.SoundMixer(null, Shaku.sfx.createSound(music2), true);

Or, we can just fade music out without fading anything in:

let mixer = new Shaku.sfx.SoundMixer(Shaku.sfx.createSound(music1), null, true);

If you want to set the mixer to a specific point in time, you can call update() with the desired progress from 0.0 to 1.0:

let mixProgress = 0.5; // valid range = 0-1
mixer.update(mixProgress);

A demo page with sounds mixer can be found here.

Conclusion

In this chapter we learned about the Sfx manager and how to use it to play sound effects and music. What we covered here is just the common use cases, to learn more about sound effects with pitch, volume, playback speed and other effects Shaku support, check out the online demos or the online docs.

Input

The input manager provides an API to query Keyboard, Mouse, Touch and Gamepad input. To access the Input manager we use Shaku.input.

This doc don't cover the entirely of the API, only the main parts of it. To see the full API, check out the API docs.

States Queries

The input manager have five main query method for key states, which can be mouse buttons, touch, keyboard keys, or gamepad buttons. These five methods are the ones you'll use the most:

  • down: return true if the button / key is currently held down.
  • released: return true if the button / key was released in this very update frame.
  • pressed: return true if the button / key was pressed down in this very update frame.
  • doublePressed: like pressed, but will only trigger after a second quick press.
  • doubleReleased: like released, but will only trigger after a second quick release.

For example, a double-click with the mouse left button can be detected like this: Shaku.input.doublePressed('mouse_left') (or doubleReleased if you prefer to only consider double click if used released the key).

In addition, all the methods above also accept an array instead of a single key code, to test if any of them match the condition. For example, the following will check if 'w' or arrow up is down: Shaku.input.down(['w', 'up_arrow']).

Input Demo

A demo page to demonstrate the input manager can be found here.

Keyboard

All keyboard keys are listed under Shaku.input.KeyboardKeys.

To query keyboard keys you can just use their names with any of the main state query methods. For example:

if (Shaku.input.down('left') || Shaku.input.down('a')) { 
  // move player left
}

In addition there are some useful keyboard-specific getters you can use: shiftDown, ctrlDown, altDown, anyKeyPressed, anyKeyDown.

Mouse

To get a mouse button state use the 'mouse_' prefix, followed by the button key (left, right, middle). For example to check if left mouse button is down:

if (Shaku.input.down('mouse_left')) { 
  // mouse left button is down
}

Mouse Position

To get mouse position use:

// returns a Vector2 instance
let mousePos = Shaku.input.mousePosition;

And to get mouse position change from last frame:

// returns a Vector2 instance
let mouseDelta = Shaku.input.mouseDelta;

Wait, mouse position is wrong?

By default the input manager will attach events to the entire document, meaning that mouse position will be relative to the top-left corner of the web page.

If your game canvas does not cover the entire screen, mouse offset will feel wrong because it won't be relative to your canvas top-left corner. You might expect to get 0,0 if you click on the canvas top-left corner, but that would only happen if the canvas starts the the page top-left corner.

To solve this, you can instruct the input manager to attach events to the main canvas (or any other element for that matter). This will also make the input manager only work when the canvas is focused, which is useful when combining Shaku with HTML UI.

To set the Input manager target element, run the following command before initializing Shaku:

Shaku.input.setTargetElement(() => Shaku.gfx.canvas);

Note that we use a callback and not Shaku.gfx.canvas directly, as Shaku.gfx.canvas will be undefined until we call Shaku.init, which is when we must call setTargetElement().

Mouse Wheel

You can query mouse wheel delta with Shaku.input.mouseWheel, or get just the mouse wheel direction (if the user scrolls up or down) with Shaku.input.mouseWheelSign (will be 0 if wheel is not used this frame).

Touch

By default, Shaku will delegate Touch input to Mouse input, so you can use the same key codes for desktop and mobile. This means that Touch start will generate a mouse click input ('mouse_left' code), and when the user drags touch input across the device, the Touch position will update as the Mouse position.

To disable this behavior, you can set Shaku.input.delegateTouchInputToMouse to false.

If you choose not to delegate Touch input to Mouse, you can use the following methods to query Touch input:

  • Shaku.input.touchPosition: Get touch last position.
  • Shaku.input.touching: True while the user is touching the device screen.
  • Shaku.input.touchStarted: True if touch input started during this update frame.
  • Shaku.input.touchEnded: True if touch input ended during this update frame.

You can get down, pressed, and released state for touch input too, similar to how you'd use a keyboard key or mouse button, but with touch as the key code:

if (Shaku.input.down('touch')) { 
  // user is touching the screen
}

if (Shaku.input.released('touch')) { 
  // touch was just released
}

if (Shaku.input.pressed('touch')) { 
  // touch was just pressed
}

Gamepad

You can query Gamepad sticks and buttons with the input manager. Note that gamepads only work after the user press any gamepad button or move the stick. This is due to browsers security limitations.

To query connected gamepad ids use:

// all ids:
console.log("Gamepads: ", Shaku.input.gamepadIds());

// by index:
console.log("Gamepad 2 id: ", Shaku.input.gamepadId(2));

// default gamepad (lowest connected index):
console.log("Default gamepad id: ", Shaku.input.gamepadId());

And to get a gamepad state object, use:

let gamepad = Shaku.input.gamepad(0); // <-- 0 is the index of the gamepad to get, or leave out this param for default gamepad

Every gamepad state object will have the following properties:

  • gamepad.axis1: Vector2 with axis1 current value.
  • gamepad.axis2: Vector2 with axis2 current value.
  • gamepad.buttonsCount: How many buttons this gamepad has.
  • gamepad.button(index): Get the state of a button (up / down).
  • gamepad.id: Gamepad id.
  • gamepad.mapping: Gamepad mapping type.

If the gamepad has a standard mapping, gamepad.isMapped will be set to true, and the following properties will also appear:

  • gamepad.leftStick: Left stick state (same as axis1).
  • gamepad.rightStick: Left stick state (same as axis2).
  • gamepad.leftStickPressed: Is left stick pressed?
  • gamepad.rightStickPressed: Is right stick pressed?
  • gamepad.leftButtons: Left side buttons cluster (top, bottom, left, right).
  • gamepad.rightButtons: Right side buttons cluster (top, bottom, left, right).
  • gamepad.centerButtons: Center buttons cluster (left, right, center).
  • gamepad.frontButtons: Front buttons (topLeft, topRight, bottomLeft, bottomRight).

Gamepad Key Codes

If Shaku.input.delegateGamepadInputToKeys is set to true (default), the Input manager will generate key-like states for all the connected gamepads that have standard mappings.

This means that instead of using the gamepad state object like we demonstrated above, you can query it directly with the main down(), pressed(), released(), doublePressed() and doubleReleased() methods.

The following keys will work supported for every connected gamepad (where X represent the gamepad index starting from 0):

  • gamepadX_top: state of arrow keys top key (left buttons).
  • gamepadX_bottom: state of arrow keys bottom key (left buttons).
  • gamepadX_left: state of arrow keys left key (left buttons).
  • gamepadX_right: state of arrow keys right key (left buttons).
  • gamepadX_leftStickUp: true if left stick points directly up.
  • gamepadX_leftStickDown: true if left stick points directly down.
  • gamepadX_leftStickLeft: true if left stick points directly left.
  • gamepadX_leftStickRight: true if left stick points directly right.
  • gamepadX_rightStickUp: true if right stick points directly up.
  • gamepadX_rightStickDown: true if right stick points directly down.
  • gamepadX_rightStickLeft: true if right stick points directly left.
  • gamepadX_rightStickRight: true if right stick points directly right.
  • gamepadX_a: state of A key (from right buttons).
  • gamepadX_b: state of B key (from right buttons).
  • gamepadX_x: state of X key (from right buttons).
  • gamepadX_y: state of Y key (from right buttons).
  • gamepadX_frontTopLeft: state of the front top-left button.
  • gamepadX_frontTopRight: state of the front top-right button.
  • gamepadX_frontBottomLeft: state of the front bottom-left button.
  • gamepadX_frontBottomRight: state of the front bottom-right button.

For example, the following will trigger when the player either press the arrow up key on the gamepad, or move the left stick all the way up:

if (Shaku.input.pressed(['gamepad0_up', 'gamepad0_leftStickUp'])) {
  alert("Move Up!");
}

Additional Parameters

The input manager support some additional parameters you can set:

  • preventDefaults: If true will prevent default input events by calling preventDefault().
  • disableMouseWheelAutomaticScrolling: If true will disable the special scroll mode that starts in chromium browsers when you click the wheel button not on a link.
  • disableContextMenu: If true will disable the context menu that opens on right mouse button click.
  • delegateTouchInputToMouse: If true will treat touch events (touch start / touch end / touch move) as if the user clicked and moved a mouse.
  • delegateGamepadInputToKeys: If true will generate gamepad key codes so you can query gamepad like you would query a keyboard key.
  • resetOnFocusLoss: If true, will reset input states when the page or target element loses focus.
  • defaultDoublePressInterval: The interval in milliseconds to consider two consecutive key presses as a double-press.

Conclusion

In this chapter we learned about the Input manager and how to use it to get input from keyboard, mouse, touch and gamepad. What we covered here is just the common use cases, to learn more about input methods, check out the online demos or the online docs.

Assets

The Assets manager handle loading and runtime creation of game assets, and is accessed by Shaku.assets. We already covered most of it while exploring the other managers, but in this section we will focus on the input manager itself.

This doc don't cover the entirely of the API, only the main parts of it. To see the full API, check out the API docs.

Assets Loading

There are two things to keep in mind while dealing with the assets manager:

Assets URL

Every asset have a unique identifier field called url. This is how we identify the asset and store it in cache. If you try to load the same asset twice, in the second call will just return the copy from the cache, provided the URL is exactly the same.

When creating assets dynamically by code, you can also provide a unique url, if you want to add the asset to cache and make it accessible anywhere via the assets manager.

Promises

Every load and create method in the assets manager returns a promise that is resolved when the asset is loaded. Even if the asset was returned from cache, it will be returned via a promise.

Note that you can query the cache directly without loading a new asset, by calling the getCached() method.

Promise.asset

All returned promises from the assets manager have additional property: asset. This property provide access the asset itself before the promise is resolved, at your own risk (since the asset may be invalid). For example, you can fetch a texture without waiting like this:

// myTexture may not be loaded yet!
let myTexture = Shaku.assets.loadTexture(url).asset;

Instead of loading it with await:

let myTexture = await Shaku.assets.loadTexture(url);

Or with then:

Shaku.assets.loadTexture(url).then((asset) => myTexture = asset);

The idea behind the Promise.asset property is to make it easier to perform parallel loading. For example, the following code creates a dictionary of assets, and wait for all to load before proceeding:

let assets = {
  tree: Shaku.assets.loadTexture("tree.png").asset,
  rock: Shaku.assets.loadTexture("rock.png").asset,
  house: Shaku.assets.loadTexture("house.png").asset
}
await Shaku.assets.waitForAll();

Just keep in mind that using an asset before its ready may cause undefined behavior and exceptions. To check if an asset is loaded and valid, you can use asset.valid.

Load a Sound

To load a sound asset:

let sound = await Shaku.assets.loadSound(url);

To learn more about sound assets, check out the API docs.

Load a Texture

To load a texture asset:

let sound = await Shaku.assets.loadTexture(url, params);

Params is an optional dictionary that accepts the following keys:

  • generateMipMaps: (default=false) if true will generate mipmaps for this texture.

To learn more about texture assets, check out the API docs.

Create a Render Target

To create a render target (a texture we can render on):

let rt = await Shaku.assets.createRenderTarget(name, width, height)

In the above example name will be used as url, ie to put the loaded render target in cache.

A render target is just a texture asset created via code. To learn more about texture assets, check out the API docs.

Load a Font Texture

To load a font texture asset:

let fontTexture = await Shaku.assets.loadFontTexture(url, params);

Params is a mandatory dictionary that accepts the following keys:

  • fontName: mandatory font name. on some browsers if the font name does not match the font you actually load via the URL, it will not be loaded properly.
  • missingCharPlaceholder (default='?'): character to use for missing characters.
  • smoothFont (default=true): if true, will set font to smooth mode.
  • fontSize (default=52): font size to generate in texture. larget font size will take more memory, but allow for sharper text rendering in larger scales.
  • enforceTexturePowerOfTwo (default=true): if true, will force texture size to be power of two.
  • maxTextureWidth (default=1024): max texture width.
  • charactersSet (default=FontTextureAsset.defaultCharactersSet): which characters to set in the texture.

To learn more about font texture assets, check out the API docs.

Load Json File

To load a json file:

let json = await Shaku.assets.loadJson(url);
console.log(json.data);

To learn more about json assets, check out the API docs.

Create Json File

You can also create a json asset dynamically, and add it to assets cache:

let json = await Shaku.assets.createJson(url, data);

Load Binary File

To load a binary file:

let bin = await Shaku.assets.loadBinary(url);
console.log(bin.data);

To learn more about binary assets, check out the API docs.

Create Binary

You can also create a binary asset dynamically, and add it to assets cache:

let bin = await Shaku.assets.createBinary(url, data);

Create a Texture Atlas

Texture Atlas was already covered when talking about the Gfx manager. To load multiple textures and generate an atlas from them, call createTextureAtlas:

let textureAtlas = await createTextureAtlas(name, sources, maxWidth, maxHeight, extraMargins)
  • name is just a unique identifier for the atlas asset.
  • sources is a list of URL to load the textures from.
  • maxWidth is an optional limit to the atlas textures width.
  • maxHeight is an optional limit to the atlas textures height.
  • extraMargins is an optional extra empty pixels to put between textures in the atlas.

Get Cached Asset

To get an already-loaded asset from cache, you can use:

let asset = Shaku.assets.getCached(url);

This method is more convinient and just return the asset immediately, without a promise. If the asset is not loaded yet, it will return null.

Waiting For Assets

There are three main properties we can use to wait and monitor assets loading progress:

Shaku.assets.pendingAssets

Return how many assets are currently waiting to be loaded.

Shaku.assets.failedAssets

Return how many assets failed to load.

Shaku.assets.waitForAll

Return a promise that will be resolved when all assets are fully loaded, or rejected if one or more failed. You can use it to load multiple assets in parallel (without await) and then call waitForAll to wait for all of them to load.

Freeing Assets

All loaded assets are stored in local cache. To free a specific asset you can call:

Shaku.assets.free(url);

Note that if you have a reference to this asset from before freeing it and you try to use it, an exception will most likely be thrown.

You can also free all loaded assets at once, by calling clearCache:

Shaku.assets.clearCache();

Collision

The Collision manager provides basic 2D collision detection, and is accessed by Shaku.collision. The collision manager handles only detection, its not a physics engine.

This doc don't cover the entirely of the API, only the main parts of it. To see the full collision manager API, check out the API docs.

Collision Shapes

A collision shape is a 2d body we use to test collision. There are currently five collision shapes Shaku supports:

Collision Flags

Every shape has a property called collisionFlags. This property is a number used as a bitmap for collision filtering. For example, you can define the following flags:

const CollisionFlags = {
  Walls: 1,
  Lava: 1 << 1,
  Floor: 1 << 2,
  Enemy: 1 << 3,
}

Then define a shape to represent a wall with the 'walls' collision flags:

shape.collisionFlags = CollisionFlags.Walls;

And later if we want to only collide with walls or laval, we can use the following collision mask:

const WallsAndLavaMask = CollisionFlags.Walls | CollisionFlags.Lava;

Collision World

A Collision World is a group of collision shapes you can query, debug render, and more. It represent a scene where you need to perform collision detection.

Every collision world have an internal grid used for the broad phase detection. Set the grid size proportionately to your collision shape sizes, so that every grid cell would only contain a handful of shapes.

Lets start by creating a collision world:

let gridSize = new Shaku.utils.Vector2(256, 256);
let world = Shaku.collision.createWorld(gridSize);

Adding Shapes

Now lets add 3 shapes to the world:

// add point to world
let point = new Shaku.collision.PointShape(new Shaku.utils.Vector2(125, 65));
world.addShape(point);

// add rectangle to world
let rect = new Shaku.collision.RectangleShape(new Shaku.utils.Rectangle(45, 75, 100, 50));
world.addShape(rect);

// add circle to world
let circle = new Shaku.collision.CircleShape(new Shaku.utils.Circle(new Shaku.utils.Vector2(300, 315), 150));
world.addShape(circle);

Picking

To 'pick' shapes that touch a position, you can use the pick() method:

// shapes will be an array with all shapes the mouse was pointing on
let shapes = world.pick(Shaku.input.mousePosition);

Or with additional parameters:

let position = Shaku.input.mousePosition;
let radius = 10;
let sortByDistance = false;
let mask = 0xffffff;
let predicate = (shape) => { return true; };
let shapes = world.pick(position, radius, sortByDistance, mask, predicate)
  • radius is radius around the position to pick shapes from.
  • sortByDistance if true, will sort results by distance from position.
  • mask optional collision mask, to match against the shapes collision flags.
  • predicate optional filter method to run in broad phase. Return false to skip a shape, or true to test it.

Collision Tests

If you want to test collision using a specific shape that isn't a point or circle, you can either use testCollision(sourceShape, sortByDistance, mask, predicate) to get a single result (faster), or testCollisionMany(sourceShape, sortByDistance, mask, predicate) for multiple results.

Resolver

The resolver is a class responsible to perform the math behind the collision detection of different shapes. If you want to create your own custom shapes, you need to register a handler method in the resolver to handle your new shape against all existing shapes.

You can access the resolver with Shaku.collision.resolver. To learn more about it, check out the API docs.

Debug Draw

Its often useful to draw the collision world when debugging. To do so, you can use the debugDraw() method:

world.debugDraw(gridColor, gridHighlitColor, opacity, camera);
  • gridColor: optional grid lines color, for cells that have no collision shapes in them.
  • gridHighlitColor: optional grid lines color, for cells that have collision shapes in them.
  • opacity: optional opacity for all shapes and grid.
  • camera: optional camera to draw the collision world with.

Note that you can set debug color for individual shapes by calling:

shape.setDebugColor(color);

physics-example

Utils

Utils is a collection of built-in utility classes and core objects we use with Shaku. You access them with Shaku.utils.

In this section we'll only mention the main objects and won't explain most of them. To learn more about them, check out the docs.

Vector2

Represent a 2d vector. For more info check out the API docs.

Vector3

Represent a 3d vector. For more info check out the API docs.

Rectangle

Represent a 2d rectangle. For more info check out the API docs.

Circle

Represent a 2d circle. For more info check out the API docs.

Line

Represent a 2d line. For more info check out the API docs.

MathHelper

Contains useful math-related functions that extend the basic JavaScript Math object. For more info check out the API docs.

SeededRandom

Generate semi-random numbers using a seed.

This is useful when you want to generate the same random numbers on different browsers and runs, based on a constant seed. For example you can use this to implement randomness in your game levels, but a consistent randomness that will generate the the same output no matter the browser or when you run it.

For more info check out the API docs.

Color

Represent a color value. The Color object store RGBA components as floats, ranging from 0.0 to 1.0.

For convenience, it also have all the built-in CSS colors as static getters, so you can easily get the following standard color values:

aliceblue, antiquewhite, aqua, aquamarine, azure, beige, bisque, black, blanchedalmond, blue, blueviolet, brown, burlywood, cadetblue, chartreuse, chocolate, coral, cornflowerblue, cornsilk, crimson, cyan, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen, darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred, darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise, darkviolet, deeppink, deepskyblue, dimgray, dodgerblue, firebrick, floralwhite, forestgreen, fuchsia, gainsboro, ghostwhite, gold, goldenrod, gray, green, greenyellow, honeydew, hotpink, indianred , indigo, ivory, khaki, lavender, lavenderblush, lawngreen, lemonchiffon, lightblue, lightcoral, lightcyan, lightgoldenrodyellow, lightgrey, lightgreen, lightpink, lightsalmon, lightseagreen, lightskyblue, lightslategray, lightsteelblue, lightyellow, lime, limegreen, linen, magenta, maroon, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, mediumvioletred, midnightblue, mintcream, mistyrose, moccasin, navajowhite, navy, oldlace, olive, olivedrab, orange, orangered, orchid, palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip, peachpuff, peru, pink, plum, powderblue, purple, rebeccapurple, red, rosybrown, royalblue, saddlebrown, salmon, sandybrown, seagreen, seashell, sienna, silver, skyblue, slateblue, slategray, snow, springgreen, steelblue, tan, teal, thistle, tomato, turquoise, violet, wheat, white, whitesmoke, yellow, yellowgreen

So for example you can get the css red color with Shaku.utils.Color.red. Note that these colors are pre-compiled and are not generated at runtime, so they will have the same values on all browsers and platforms, even if some of them decide to implement different values for the css keyword.

For more info check out the API docs.

Animators

Animators are objects you can attach to any JavaScript object and animate any property in it over time. Animators can also register themselves to update automatically, so you don't need to update them and can just launch and forget about them.

Note that Animators are not exclusive to Shaku objects, they are generic and work on anything.

Let's start with a simple animator to create a heartbeat-like pulse that runs once, then we'll explain the code:

// create an animator that will grow 'sprite' to x1.5 of its size, then shrink it back to original size over the course of 0.5 seconds per direction (grow / shrink).
// smoothDamp means the animation won't be linear, it will be faster at the beginning and slower as we reach the target size
let heartbeat = (new Shaku.utils.Animator(sprite))
				.to({'size': sprite.size.mul(1.5)})
				.reverseBackToStart()
				.duration(0.5)
				.smoothDamp(true);

// run the animation once
heartbeat.play();

Lets review the code above:

  1. First we create an animator that takes sprite as its target (assume its a Sprite object we created earlier).
  2. Next we set a target values with to(), instructing the animator to change the 'size' property to x1.5 its starting value (Animators know how to handle the Vector2 objects and can lerp their x and y components).
  3. Then we instruct the animator to animate back to the starting state with reverseBackToStart(), so it will grow and then shrink back to original size. The animator keeps the value of all the properties it animates when the animation begins, so it knows how to reverse back to start. Note that we don't even know the size of the sprite, nor does it matter.
  4. Next we set the animation duration in seconds - 0.5 seconds to grow, 0.5 seconds to shrink.
  5. And finally we set mode to smoothDamp(), meaning animation will not be linear but will start faster and slow dow