Hey!, hope you are doing well!, im good, This post should have gone up in the month of august and this post should have been a follow-up, anyway never too late. Update: Im following a tight schedule for the past few weeks, tight-schedule in the sense “wake-up -> commute-to-office -> professional code-> aftier-office -> passion code -> sleep” throw in breaks, travel, and Personal hygene time in there, at-first it was awful, but after some while got the hang of it,Infact it became like addiction to me at this point & im craving for more.


why & what

So in the month of september a friend of mine asked me whether I can Join him in making game, till that point in time, I haven’t done game development. So the gamejam was js13kgames, though it ran for a month we went into working only in the last seven days.

development stack

javascript, gulp for automated tasks,

apart from the time constraint of a month to finish, this also included a constraint which is, the total size of the game must be less than 13 kilobytes and a topic to develop unde, which is not quite a lot. Everything developed for the competition was from scratch except the sound library.

challenges

Though the gamejam was for a month, the actual dev period was for 7 days, to be honest it was more like 2-3 days of dev. (Project mate) FR0ST1N was sick during the last few days so the game was not complete, I made the decision to submit it anyway.

This post is gona be the disection of different parts of the game.

UI

Pixel art for game images, since we were limited on the size aspect of the game, we could not have high res image assets.Spritesheets were used for the development.

There is a seperate class for parsing the spriteSheet & maintining it states based on the sprite attributes.


    /*
     * Porperties the sprite has to manipulate it
     */

    const image = new Image();
    image.src = 'images/' + imagename;
    this.image = image;
    this.rows = rows;
    this.columns = columns;
    this.individualSpriteSize = individualSpriteSize;
    this.totalSpriteSize = totalSpriteSize;
    this.position = position;
    this.scaleFactorX = scaleFactorX;
    this.scaleFactorY = scaleFactorY;

animation

Bare api calls, small framework that can be used in future projects.

Explanation on how that works.

Core is based on values defined by you, fps is set accordingly & callbacks are called, these callbacks are responsilbe for object-animation & object movement, all logic should be done with those callbacks.

following this principle, animation was done to the most part, code for the same is below

Main callbacks from the animation class


/*
 * Animation properties that all object in canvas should have,
 * ex |------------------------| represent total time
 *    |-----x------x------x-----| represent time to next frame.
 */

  /**
   * reset the animation timer,
   * @return {void}
   */
  _resetAnimationTimer() {
    this.animationTimer = 0;
  }


  /**
   * reset the animation timer,
   * @return {void}
   */
  _resetMotionTimer() {
    this.motionTimer = 0;
  }

  /**
   * @return {void}
   */
  stepTimer() {
    if (this.timeToChangeAnimation != 0) {
      this.animationTimer += 1;
    } if (this.timeToChangeMotion != 0) {
      this.motionTimer += 1;
    }
  }

  /**
   * @return {boolean}
   */
  isTimeToAnimate() {
    if (this.timeToChangeAnimation == 0) {
      return true;
    } if (this.animationTimer == this.timeToChangeAnimation) {
      this._resetAnimationTimer();
      return true;
    }
    return false;
  }

  /**
   * @return {boolean}
   */
  isTimeToMove() {
    if (this.timeToChangeMotion == 0) {
      return true;
    } if (this.motionTimer == this.timeToChangeMotion) {
      this._resetMotionTimer();
      return true;
    } else {
      return false;
    }
  }

Main callbacks from timer


  /**
   * wrapper Draw.
   */
  wDraw() {
    this.context.drawImage(
        this.sprite.image,
        this.sprite.position.x,
        this.sprite.position.y,
        this.sprite.individualSpriteSize,
        this.sprite.individualSpriteSize,
        this.position.x, this.position.y,
        this.sprite.individualSpriteSize * this.sprite.scaleFactorX,
        this.sprite.individualSpriteSize * this.sprite.scaleFactorY
    );
  }

  /**
   * override this method in child classe, for animation logic.
   * when it is time for next frame, this method will be called.
   */
  objectUpdate() {
  }

   /**
   * makes decisions whether to redraw,
   * based on the fps defined.
   */
  postObjectUpdate() {
    this.stepTimer();
    if (this.isTimeToMove()) {
      this._Movement();
    } if (this.isTimeToAnimate()) {
      this.objectUpdate();
    } this.objectAnimation();
  }

  /**
   * object movement loop
   */
  objectAnimation() {
    this.animationTimerId = setTimeout(() => {
      this.postObjectUpdate();
    }, 1);
  }


Where I struggled

The level designing was the part that i struggled the most, since from what we had to work with, I was finding it hard to come up with a good gameplay anyhow based on the time constraint (two hours before deadline for the game), I came up with a level design involving two fire-animations (straight pattern & follow pattern)

following is the level logic that I cameup & implemented in 15min.


  //@level logic from class level

  /**
   * design
   * level one:
   *  one enemy, straight bullet, health = 1, rateOfFire = (1000-100/level=1)
   * level two:
   *  two enemy:
   *    1st enemy: straight bullet, health = 2, rateOfFire = (1000-100/level=2)
   *    last enemy: follow bullet, health,rateOfFire=same as 1st enemy
   * ... progressive, all the last enemy has the follow bullet
   */
  levelTrigger() {
    for (let enemyCounter = 1; enemyCounter <= this.level;
      enemyCounter++ ) {
      const enemy = this.enenmyOneFactory(
          new Position(800, enemyCounter * 50 + 100),
          this.level,
          (1000 - 100) / this.level);
      enemy.startAnimation(this.context);
      enemy.autoshoot = true;
      if (this.level != 1 && enemyCounter == this.level) {
        enemy.setBulletPattern(BulletPattern.FOLLOW);
      }
      enemy.shoot();
      this.currentLevelEnemies.push(enemy);
      this.drawableObjects.push(enemy);
    }
  }

collision Detection

Collision detection was straight forward too, you just compare the two rectangles.


  /**
   * collideDetect of enemyObject with given bullet
   * @param {Bullet} bullet
   * @return {boolean}
   */
  collideDetect(bullet) {
    if (this.position == null || bullet.position == null ) {
      return false;
    }
    const bulletObject = {'x': bullet.position.x,
      'y': bullet.position.y,
      'width': bullet.sprite.individualSpriteSize * bullet.sprite.scaleFactorX,
      'height': bullet.sprite.individualSpriteSize * bullet.sprite.scaleFactorY,
    };
    const EnemyObject = {
      'x': this.position.x,
      'y': this.position.y,
      'width': this.sprite.individualSpriteSize * this.sprite.scaleFactorX,
      'height': this.sprite.individualSpriteSize * this.sprite.scaleFactorY,
    };
    return CollisionDetection.detect(bulletObject, EnemyObject);
  }

Sound

Small library extending from previous year’s.

AudioEffects.js

Fonts

Even using texts take a huge hit on the size of the game, we found a custom font implementation & modified it to fit our usecase, since the font class mainly contains hardcoded values for drawing fonts to the canvas, I have linked just the source below.

[font.js](https://github.com/FR0ST1N/Pew-Pew/blob/develop/src/js/font/font.js)

the fonts can be used by the following way


this.ctx.fillStyle = '#00bff3';
const CORRECTION = (value.length - 1) * 20;
Font.draw(value, size, this.ctx, 775 - CORRECTION, 10);

Build

For automating the build process gulp was used, the commands from gulp are pretty straight forward, can be understood just from telling the commands.


gulp.task('cssLint', function() {
  return gulp.src('src/css/*.css')
      .pipe(cssLint({
        reporters: [{formatter: 'string', console: true}],
      }));
});

gulp.task('jsLint', function() {
  return gulp.src('src/js/**/*.js')
      .pipe(concat('main.js'))
      .pipe(jsLint())
      .pipe(jsLint.format())
      .pipe(jsLint.failAfterError());
});

gulp.task('htmlBuild', function() {
  return gulp.src('src/index.html')
      .pipe(htmlMin({
        collapseWhitespace: true,
        removeComments: true,
        html5: true,
      }))
      .pipe(gulp.dest('build'));
});

gulp.task('cssBuild', function() {
  return gulp.src('src/css/*.css')
      .pipe(concat('style.min.css'))
      .pipe(cssMin())
      .pipe(gulp.dest('build/css'));
});

gulp.task('jsBuild', function() {
  return gulp.src(jsConcat, {allowEmpty: true})
      .pipe(concat('main.js'))
      .pipe(jsMin({
        noSource: true,
        ext: {
          min: '.min.js',
        },
      }))
      .pipe(gulp.dest('build/js'));
});

gulp.task('imageMin', function() {
  return gulp.src('src/images/*')
      .pipe(imageMin({optimizationLevel: 5}))
      .pipe(gulp.dest('build/images'));
});

gulp.task('zip', function() {
  const maxSize = 1024 * 13;

  return gulp.src('build/**')
      .pipe(zip('Pew-Pew.zip'))
      .pipe(gulp.dest('zip'))
      .pipe(checkSize({fileSizeLimit: maxSize}));
});

gulp.task('lint', gulp.parallel(
    'htmlLint',
    'cssLint',
    'jsLint'
));

gulp.task('build', gulp.series(
    'lint',
    'clean',
    'htmlBuild',
    'cssBuild',
    'jsBuild',
    'jsfxr',
    'imageMin',
    'zip',
    'genDocs'
));

submission - conclusion

During the last day, 80% of the game was completed, everything was to be put together, except that my team mate was very ill & could not work on the project.

We almost did not submit because the game cuz ‘incomplete, except we did submit the game that was too because I insisted on submitting,

My friend & partner in the project. FR0ST1N

At the end, It was fun..