Site Logo


Welcome to my Danmakanvas information page!

What is Danmakanvas?

Danmakanvas is a lightweight danmaku engine built in JavaScript (for HTML5 Canvas) that is embedded within this website's tutorials to provide visual examples of danmaku patterns. It provides bullets, as well as simulated tasks and Single scripts.

Danmakanvas was first written May-June 2016 (v1) and was, to be frank, OK at best, with a plethora of SetInterval problems and limited usage. However, in January 2018, this old project was slightly rewritten and added to this website (v2) in a form that was usable... if there was only one canvas per page. In April-May 2018, Danmakanvas was rewritten to allow for multiple canvases on the same webpage.

Embedding in other websites

WARNING: Danmakanvas may break at any time. We recommend using the latest stable version. In addition, if any bugs are discovered, please report them.

Interested in using Danmakanvas on your website? Here's how!

First, you must include the specified version of Danmakanvas that you want to utilize. It is recommended that you utilize the latest version. Note that different major versions of the application may have different requirements. Refer to the documentation section below.


To place a canvas, do the following:

<canvas id="gamecanvas_1" width="384" height="448" style="border:1px solid #DDDDDD;">
<button type="button" onclick="createNewGame('gamecanvas_1', 'String to display')">Run Danmakanvas Simulation</button>

The ID of the canvas DOM element should match the id passed as a parameter to createNewGame(). By default, whatever string is used for 'String to display' will be displayed in the top left of the canvas.

Note that the background color of the canvas is not specified in the JavaScript file and will default to whatever background the canvas is laid onto.

Additionally, note that you do not need to use a button to trigger the canvas - you can call the createNewGame() function wherever you want.

Documentation (2.1)

In this section, we will describe how to utilize Danmakanvas's functionality.

Danmakanvas creates game instances linked to canvases. Each game instance refreshes at 50 FPS and contains the following: a player object, an array of bullets, and a plural object. createNewGame() creates a new game instance and starts it - setting up the update interval, the draw and update loops, and getting the plural instance.

Plurals and Singles are defined in a Plural file. Each plural file must implement getPluralController(currgame, canvasid), which takes the current game object and canvas ID object. This function is called by the game engine. The function must return a new Plural object. Example:

//Controller that determines which attacks to display
function getPluralController(currgame, canvasid) {
    switch(canvasid) {
        case "gamecanvas_1":
            return new Plural_1(currgame);
        case "gamecanvas_2":
            return new Plural_2(currgame);
        case "gamecanvas_3":
            return new Plural_3(currgame);
            console.log("getPluralController(): Canvas ID " + canvasid + " could not be recognized. Please check to make sure that the canvas ID is correct and/or supported.");

Plural Objects are created by functions as follows:

//Constructor for a Plural
function Plural_1(currgame) {
    var singles = [new Single_1(currgame)];
    this.step = 0; //Starting single
    this.update = function () {
    this.remove = function () {
        singles = [];

As can be seen above, each Plural Object contains an array of Single objects. Every Plural Object must implement update() (to have the Single objects update) and remove() (for cleanup).

The Single Objects control the actual firing of the bullets utilizing an admittedly very bad 'coroutine' implementation. A Single can be constructed similarly to the following:

//Constructor for a Single
function Single_1(currgame) {
    var tasks = [];
    //Push Starting/Continuous Tasks here:
    var i;
    for (i = 0; i < 6; i += 1) {
        tasks.push(new Single_1_Task_Shiki(i, 6, 1, 48, "aquamarine", "blue", currgame));
        tasks.push(new Single_1_Task_Shiki(i, 6, -1, 96, "pink", "red", currgame));
    //In update, push tasks that run every x frames
    this.update = function () { //Main Loop
        //Remove completed tasks
        var tasktoremove = [];
        var i;
        for (i = 0; i < tasks.length; i += 1) {
            if (tasks[i].finished) {
        for (i = tasktoremove.length - 1; i >= 0; i -= 1) {
            tasks.splice(tasktoremove[i], 1);
    this.remove = function () {
        tasks = [];

Similarly to Plural objects, each Single must implement an update and remove. Each single has a list of tasks - these are the 'coroutines' that run on update. These tasks are objects that are pushed into an array within the Single, and the Single's update handles them - removing them when they are complete, spawning new tasks, etc.

Finally we have tasks. Each task has an update that handles creating bullets, and a reset to forcefully reset the task. See the example below:

function Single_1_Task_Shiki(ID, numinring, dir, rad, color, color2, currgame) {
    this.counter = 0;
    this.maxcounter = -1; //maximum time allowed for task to run. Use -1 for non-terminating tasks
    this.finished = false;

    this.angleT = ID*Math.PI*2/numinring;
    this.update = function () {
        //Comment out counter check for nonterminating tasks
        this.angleT = ID*Math.PI*2/numinring + this.counter*0.02*dir;
        var shikix = 192 + rad*Math.cos(this.angleT);
        var shikiy = 224 + rad*Math.sin(this.angleT);
        //render shiki as a bullet that lasts one frame
        var selfshot = new EnemyShot(shikix, shikiy, 0, 0, 0, 5, color2, 8, 4, 1, 4, 1, currgame);
        if (currgame.everyinterval(20)) { 
            var i;
            for (i = 0; i < 3; i++) {
                var newshot = new EnemyShot(shikix, shikiy, 1.5, this.angleT + Math.PI + i * toRadians(120), 0, 5, color, 3, 5, 0.75, 4, -1, currgame);
        this.counter += 1; //increment counter
        if (this.counter === this.maxcounter) {
            this.finished = true;
    this.reset = function () { //Deconstructor. Reset to original values in preparation for removal (except for finished). Called by the Single object
        this.counter = 0;
        this.maxcounter = 0;
        this.angleT = 90;

It is recommended that reset be left as-is. Now, we will discuss timing and shot creating via everyinterval and the EnemyShot object. These are both implemented in the game engine.



Only versions past 2.1 have their changes documented here.

Source code and usage examples can be found on Github.

v2.1.3 [Sep 11, 2018]

* Bullet Count text now uses canvas height instead of hardcoding.

v2.1.2 [Sep 10, 2018]

* isinbounds now uses canvas dimensions instead of hardcoding.

* Version number now stored at top of file as variable for easy access and reference.

v2.1.1 [May 03, 2018]

* Fixed issue where clicking 'Run Danmakanvas Simulation' multiple times would start new instances of the simulation on the same canvas. Issue was a porting issue from 2.0, where startedplurals was not global and was accidentally made local to a game instance (where it was effectively useless). New clicks no longer boot up a new instance. [Thanks to Arcvasti]

* Note that the 2.0 behavior of reset-on-click is not implemented in 2.1.1 and will require more changes in order to work. Whether or not this feature is necessary is a different issue entirely.

v2.1 [Apr 29, 2018]

* New version of Danmakanvas that has each game as a new instance, allowing for multiple canvases on the same page.

* Plural/Single format is different and old scripts for 2.0 are not compatible with 2.1, as information on the current game must be threaded into the plurals and singles.