This is part 4 of an Ionic 4 tutorial. Please read part 1, part 2 and part 3 before continuing here…

Part 4: Make it sound x-massy

To let our app sound x-massy, I’ve prepared some audio files that you can download here:
SantaClausIsComing.mp3
Hohoho.mp3

After downloading the files copy them into the assets folder.

 

Embed audio

There are various ways of integrating audio in an app. We make it easy with the howler.js library.

Install howler.js in the terminal with

$ npm install howler


Then we create a service with

$ ionic g service services/audio


In the new audio.service.ts we code the following lines:

import { Injectable } from '@angular/core';
import { Howl } from 'howler';

@Injectable({
  providedIn: 'root'
})
export class AudioService {

  private hohoho: Howl;

  constructor() { }

  Init() {

    new Howl({
      src: ['../assets/SantaClausIsComing.mp3'],
      preload: true,
      autoplay: true,
      loop: true
    });

    this.hohoho = new Howl({
      src: ['../assets/Hohoho.mp3'],
      preload: true,
      autoplay: false,
      loop: false
    });
  }

  SayHohoho() {
    this.hohoho.play();
  }

}
 

Now we can import and initialize this service in home.page.ts:

import { Component } from '@angular/core';
import { AudioService } from '../services/audio.service';
import { GameService } from '../services/game.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  constructor(
    public audio: AudioService,
    public game: GameService
  ) { }

  ngOnInit() {
    this.audio.Init();
    this.game.Start();
  }

}
 

Since our SantaClausIsComing song was configured with the parameter autoplay: true, the song starts already, as soon as the app is restarted.

 

Trigger audio in a specific situation

With howler.js it’s easy to trigger a sound to a certain event.

In our app it makes sense that Santa calls “hohoho!” when he picks up one of the presents.

So we go to our game.service.ts file and add the highlighted lines:

import { Injectable } from '@angular/core';
import { MazeService } from '../services/maze.service';
import { MazePosition } from 'src/app/models/maze-position';
import { ToastController } from '@ionic/angular';
import { AudioService } from '../services/audio.service';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class GameService {

  public status: string;

  /** player: position */
  private playerPosition: MazePosition = new MazePosition(0, 7);

  /** items: positions */
  private itemPresent1 = new MazePosition(7, 1);
  private itemPresent2 = new MazePosition(8, 15);
  private itemPresent3 = new MazePosition(12, 8);

  /** items: collected */
  private hasPresent1: boolean = false;
  private hasPresent2: boolean = false;
  private hasPresent3: boolean = false;

  constructor(
    private audio: AudioService,
    private maze: MazeService,
    private toastController: ToastController
  ) { }

  /**
   * Starts the game.
   */
  public async Start() {
    this.maze.Init(16, 16);
    this.initPlayer();
  }

  /**
   * Navigates with the arrow keys or WASD.
   * 
   * @param keydownEvent.code event code coming from keydown
   */
  public Navigate(keypressEvent) {
    switch (keypressEvent) {
      case 'ArrowUp':
      case 'KeyW':
        if (this.maze.CanMoveUp(this.playerPosition)) {
          this.movePlayer(this.playerPosition.Col, this.playerPosition.Row - 1);
        }
        break;
      case 'ArrowLeft':
      case 'KeyA':
        if (this.maze.CanMoveLeft(this.playerPosition)) {
          this.movePlayer(this.playerPosition.Col - 1, this.playerPosition.Row);
        } else {
        }
        break;
      case 'ArrowDown':
      case 'KeyS':
        if (this.maze.CanMoveDown(this.playerPosition)) {
          this.movePlayer(this.playerPosition.Col, this.playerPosition.Row + 1);
        }
        break;
      case 'ArrowRight':
      case 'KeyD':
        if (this.maze.CanMoveRight(this.playerPosition)) {
          this.movePlayer(this.playerPosition.Col + 1, this.playerPosition.Row);
        }
        break;
    }
  }

  private initPlayer() {
    this.movePlayer(this.playerPosition.Col, this.playerPosition.Row);
  }

  private movePlayer(col: number, row: number) {
    if (row < 0) {
      this.presentToast('Hey!', 'What kind of unreliable Santa are you?');
    }
    else if (row < this.maze.Rows) {
      this.moveSection('santa',
        col * (this.maze.CellPixels),
        row * (this.maze.CellPixels)
      );
      this.playerPosition = new MazePosition(row, col);
      this.checkForItemHits();
    } else {
      if (!this.hasPresent1 || !this.hasPresent2 || !this.hasPresent3) {
        this.presentToast('Hey!', 'You haven\'t found all the presents yet, Santa!');
      } else {
        this.audio.SayHohoho();
        this.presentToast('Ho ho ho!', 'You\'ve found all the presents, Santa!\nChristmas can come now ;-)');
      }
    }
  }

  private moveSection(elementName: string, xOffset, yOffset) {
    var element = document.getElementById(elementName);
    if (element) {
      var transformAttr = ' translate(' + xOffset + ',' + yOffset + ')';
      element.setAttribute('transform', transformAttr);
    }
  }

  private checkForItemHits() {
    if (this.playerPosition.Col == this.itemPresent1.Col
      && this.playerPosition.Row == this.itemPresent1.Row) {
      this.hasPresent1 = true;
      this.audio.SayHohoho();
      this.presentToast('Hey!', 'You\'ve found present 1, Santa!');
      this.hide('present1');
    }
    if (this.playerPosition.Col == this.itemPresent2.Col
      && this.playerPosition.Row == this.itemPresent2.Row) {
      this.hasPresent2 = true;
      this.audio.SayHohoho();
      this.presentToast('Hey!', 'You\'ve found present 2, Santa!');
      this.hide('present2');
    }
    if (this.playerPosition.Col == this.itemPresent3.Col
      && this.playerPosition.Row == this.itemPresent3.Row) {
      this.hasPresent3 = true;
      this.audio.SayHohoho();
      this.presentToast('Hey!', 'You\'ve found present 3, Santa!');
      this.hide('present3');
    }
  }

  private hide(elementName: string) {
    var element = document.getElementById(elementName);
    element.style.display = "none";
  }

  private async presentToast(header: string, message: string) {
    const toast = await this.toastController.create({
      header: header,
      message: message,
      duration: 5000,
      position: 'top'
    });
    await toast.present();
  }

}
 

So, give it a try and be happy with Santa when he finds the presents in the maze and finally the exit 😉

 

Improvements

There is of course a lot of room for improvement in this gaming app:

  • A nice sound when Santa takes a step and/or another sound when he hits a wall.
  • And other characters (bots) that make life difficult for Santa because they also chase after the gifts and snap them away from him.
  • And so on…

Feel free to implement further features and share them with us!

 

Credits

Without the great graphics, tools and audio files, which are available free of charge on the web, I would have had to spend a lot more time on this app. Therefore I would like to finally thank:

  • mazegenerator.net
    With this amazing tool you can generate all kinds of labyrinths … and export them as SVG!
  • freepik.com
    This is where the pretty decoration, our Santa and the presents come from. Thanks to kipargeter for this!
  • soundcloud.com
    The song “Santa Claus is coming” is by Andi del Mar and is freely available on SoundCloud.

I hope you enjoyed this tutorial and you’ve found some inspiration for your own awesome apps. You want more of this cool stuff? Then look at my Ionic 4 book.

Happy reading and coding!

Leave a comment

Your email address will not be published. Required fields are marked *