top of page
Search
cedarcantab

Side Note: Cleaning Up The Danmaku Code

No new danmaku patterns this time. Instead, I have decided to make a lot of changes to the way the code is written, as it was getting increasingly difficult to add functionality. I am documenting the key changes made for the record.


Destructuring assignment of function parameters

From quite early on, I had switched from using individual variables to objects to pass parameters to functions. That had made it possible to pass parameters to functions without caring about the order in which they are passed. I have gone one step further and used destructuring assingment to set default values and read parameters. This does not make the code any more compact (if anything, the code is longer). However, I have now listed out all the parameters to set their default values, and put in more substantial comments, hopefully making it easier for myself to remember what the individual parameters are supposed to do.


For example, the Bullet class fire function now looks like below. Among all the changes, I have added the ability to specify frames, in case the texture used is a spritesheet with multiple frames. I have also added a new parameter called "bulletType". At the moment, this is used to distinguish between only 2 types:

  • "NORMAL": bullets that fly straight

  • "BEND": bullets that fly along a curved path, dictated by the angularVelocity.

In the pervious version of the code, I had used a boolean flag to decide whether a bullet is a bending type or not. However, I made this change since I will at some point add a third type of bullet that "homes in" on the target.


In order to accommodate the above change, what was called BulletType is now BulletTexture, a name that is more in line with the Phaser convention anyway. At the same time I have also added a BulletFrame parameter so that bullets image can user individual frames from spritesheets. To test this new feature, I have poached a spritesheet called "balls", as usual from the Phaser examples, and used it for the bullets fired by the player.


fire({
    // set defaults for the all the acceptable parameters
    x = 0, // origin of bullet spawn
    y = 0, // origin of bullet spawn
    bulletTexture = null, // texture to be used for the bullet image
    bulletFrame = null, // if the texture is a spritesheet, then specify frame to be used
    shootAngle = 0, // direction in which the bullet is fired
    bulletSpeed = 0, // the speed of the bullet
    bulletAcceleration = 0, // acceleration of bullet
    bulletAngle = 0, // this is the angle of the bullet image
    bulletAngleVelocity = 0, // this is the angular velocity of the bullet image
    bulletType = "NORMAL" // this will indicate way the bullet behaves; NORMAL, BEND and HOMING (not yet written)
  }) // end of setting defaults for parameters for the fire function 
  { // this is the beginning of the fire function
    
    // set some basic properties of body and game object when first fired
    this.enableBody(true, x, y, true, true);
    this.body.collideWorldBounds = true;
    this.body.onWorldBounds = true;
    this.setTexture(bulletTexture); // set the texture of the bullet
    this.setFrame(bulletFrame);

    // initialise various attributes for the bullet when first fired
    this.shootAngle = shootAngle; // keep record of the original shoot angle.
    this.angle = shootAngle; // the initial angle of the bullet image should be the shoot angle
    this.body.angularVelocity = bulletAngleVelocity; //

    this.bulletType = bulletType;
    this.bulletSpeed = bulletSpeed;
    this.acceleration = bulletAcceleration;
    // when first fired, set the velocity (vector) of the bullet in accordance with the speed (bulletSpeed) and direction (shootAngle)
    this.scene.physics.velocityFromAngle(
      this.shootAngle,
      this.bulletSpeed,
      this.body.velocity
    );
    // when first fired, set the acceleration vector of the bullet in accordance with the acceleration passed as parameter and direction (shootAngle)
    this.scene.physics.velocityFromAngle(
      this.shootAngle,
      this.acceleration,
      this.body.acceleration
    );
  } // end of fire function

Lots of change made to the danmaku class

I have made a LOT of changes to the danmaku class. The main changes are:

  • The bulletgroup to be recycled is now passed to the danmaku as a parameter - this change was made so that the danmaku may be used by the main player object as well as the enemy objects.

  • Partly to accomodate the above change, I have done away with the parent (DanmakuCore) and sister class (DanmakuWrapper), and now only have one Danmaku class.

  • Used destructuring assignment to read the parameters

  • A significantly reduced number of parameters is sent to the constructor function; specifically: scene, bullet group, x, y, texture for the danmaku object (typically not used), and frame for the danmaku object (again, not used)

  • All the other parameters that define the danmaku patterns are now set using a new method called "setProperties". Hence, whereas all the detailed parameters were sent to the danmaku class when the new danmaku object was instantiated, now the "configutaion" of the danmaku pattern will be performed, after the danmaku object is instantiated, by calling the setProperties method with all the relevant parameters. This will also allow the danmaku to generate changing patterns, after it is created.

  • Added numberOfRounds as a new parameter to danmaku. Default is -1, which means danmaku fires continuously. However, this can now be set to any other number n, so that the firing will cease after n-rounds. If n is set to 1, the danmaku can be used to shoot single bullets. Combining with another timer created by the enemy class for example, intermittent danmaku patterns can be created.

  • Some of the object/variable names have been changed. For example, the the timer name changed from machineGun to repeatFireTimer, simply because I felt like it.

  • Added a new parameter "danmakuType" which by default is set to "NWAY". At the moment, this parameter is only used to detect if a washer tween needs to be set. In the prior version of the code, the washer parameter object was nested, but with destructuring assignment I could not get it work if a null parameter was passed. In anycase, in developing different danmaku patterns, we have basically been building on the "NWAY" pattern. However, when we get to the stage of developing patterns that are not derivatives of NWAY, then we will utilise this parameter further.

  • Added a "name" parameter. Typically would not be used except for future demo where I intend to cycle through set danmaku patterns, and displaying the name of the danmaku patterns.


Danmaku is instantiated by the enemy object


The code has been re-written so that a danmaku object is instantiated by the individual game characters. The danmku's follow method will ensure that the danmaku will stay with the game characters, when they move.


The re-written Enemy class is shown below - it illustrates how the re-written danmaku object is instantiated by the game character object and then "configured" by calling the setProperties method.


The this.firing boolean is not used in this particular example, nor is the associated fireDanmaku function. The this.firing varilable can be used for example, when you have moving enemies that do not fire until the enemy has moved to its prescribed firing position. The fireDanmaku function can be used when the enemy character fire intermittently; for example, the numberOfRounds can be set to say 5, and another timer is created by the enemy object to call the fireDanmaku method.


class Enemy extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, munitions, { x = 0, y = 0, texture = null, frame = null }) 
  {
    super(scene, x, y, texture, frame);
    // Add the game object to the scene
    scene.add.existing(this);
    // Add the physics body to the scene
    scene.physics.add.existing(this);
    this.munitions = munitions;
    this.damage = 0;
    this.firing = false; // this flag is used to stop the enemy firing until it is in position to fire

    this.danmaku = new Danmaku(scene, this.munitions, {});

    this.danmaku.setProperties(scene, {
      // properties for cannon(s)
      numberOfCannons: 1, // number of (pair in case of bi-directional danmaku) cannons
      cannonAngle: 90, // direction in which the primary cannon faces.
      cannonAngleRange: 40, // used to n-way type. angle range across which cannons are positioned.

      numberOfRounds: -1, // how many times to shoot the weapon in succession
      // properties for individual bullets
      bulletType: "NORMAL",
      bulletSpeed: 200, // speed of bullets fired from cannon
      timeBetweenBullets: 200, // time between bullets in ms
      bulletTexture: "bullet8", // specify the bullet image to be used,

      // properties for washer type
      washer: {
        status: true,
        swingRange: 30,
        cycleLength: 500,
        swingType: "Linear"
      }
    }); // end of parameters

    this.danmaku.triggerRepeatFire(scene); // set the danmaku firing (set to fire indefinitely in this example)
    
  } // end of Enemy constructor

  preUpdate() {
    this.danmaku.follow(this);
  }

  fireDanmaku(scene) {
    if (!this.firing) {
      return;
    }
    this.danmaku.triggerRepeatFire(scene);
  }
}

Player game object now also utilise danamku to fire


With the changes made to the danmaku class (most important being the ability to specify which bulletgroup to use), the player can now utilise the danmaku object to fire bullets.


I have increased the maximum number of objects in the playerBullets group to 100 from 10 so that the player can now fire more inventive bullet patterns!


The entire revised code is available here, for your perusal.


Comentarios


記事: Blog2_Post
bottom of page