Quantcast
Channel: GameDev Academy
Viewing all 1620 articles
Browse latest View live

Designing Assets in Unity with ProBuilder

$
0
0

Introduction

In this tutorial, we will be exploring how to model and build objects in Unity using ProBuilder. This tutorial has two sections – in the first, we will be building a simple, low poly, car. In the second section, we will be utilizing something called “Bezier Curves” to make a race track. The things we are making are not complex or intricate, but the concepts that you will learn can be applied to more complex and intricate projects.

What is ProBuilder?

We will be using two components in Unity, ProBuilder and ProGrids. ProBuilder is a tool that gives Unity the ability to edit the shape of primitive objects. This allows us to make more complex gameobjects. And ProGrids is a tool (similar to the Snap Settings in Edit -> Snap Settings) that will snap whatever it is we are doing, whether it be editing, scaling or rotating, to a grid. This allows for a very uniform look on our gameobjects. This tutorial is mainly going to focus on ProBuilder.

Requirements

This tutorial assumes that you have hardly any knowledge about modeling gameobjects, however, prior knowledge of Unity (like creating and manipulating primitive objects) is definetely going to help.

Importing ProBuilder and ProGrids

Create a new Unity project and go to the Asset Store. Search “ProBuilder”, it should be the one of the first items.

Download and import this. Next, search “ProGrids”, this one should also be one of the first items.

Download and import this one as well. If everything imported correctly you should see ProBuilder and ProGrids appear in the “Tools” drop down. Let’s go ahead and create a new folder called “Scenes” and then create a new scene called “CarAndTrack”.

Familiarizing ourselves with ProBuilder

Go to Tools -> ProBuilder -> ProBuilder Window.

Place this new tab some where convenient, I put mine here.

ProBuilder has two different ways of looking at its window, Icons and Text. You can switch between the two by right-clicking in the window and selecting which one you want, I usually stick with text since it is more descriptive that an image.

In your scene view you’ll notice a new bar on the top part of this tab that has four different buttons.

These are our different ways of interacting and editing our objects. You will change this multiple times throughout your project. Right now it is set to the “Object” mode. This allows us to move, rotate, and scale an object with out changing the structure of the object. The other options allow you to manipulate either the vertices, edges, or faces of an object. If you aren’t sure what a face or vertex is don’t worry, you should understand what they are as you move through this tutorial. Let’s take a look now at ProGrids. You’ll notice a new side bar has been created in the scene view. The ones settings we are going to be mainly using are theses right here:

The top one enables or disable ProGrids, we will be using this one mostly. The last few are mainly which grid is displayed in the scene view, I prefer the Y plane over the others since it is mainly what I am used to. Feel free to change these last ones to be however you’d like. Now that we have had a brief look at how ProBuilder and ProGrids work, let’s start creating!

Building the body of our Car

With ProGrids enabled, click “New Shape” in the ProBuilder window.

This will bring up a new window in which we can specify what shape we want built. The car we are going to make will very “blocky” so we will use the Cube shape. Leave everything set to default and click “Build Cube”.

If you have any experience modeling 3D objects then go ahead and start “blocking” out a car shape, with wheel wells and windshields. If you aren’t too familiar with building objects from primitives, follow along, you’ll get the hang of it pretty quickly. With our cube selected, I am going to go to face select mode and select one of the faces that lie on the X axis. Then I am going to drag this face out one grid space (with ProGrids enabled) so that it looks like this.

I am then going to grab one of the faces that lie on the Z axis and drag it out one grid space as well.

Then I am going to extrude this face one grid space by clicking “Extrude Faces” in the ProBuilder window.

We can snap this to the grid by dragging it forward. I am now going to make the windshield by going to edge select mode and grabbing the top edges of this newly extruded face and dragging it upwards.

It looks a little steep so I am going to drag it backwards along the Z axis.

To finish it up I am going to grab the bottom edges and make them match with the top ones.

Next, let’s extrude the back face to make the roof and the back window.

Here I am going to disable ProGrids for a moment so that I can edit this in freehand. I am going to make the back window not as steep as the windshield.

 

While ProGrids is off, I am going to make the hood shorter, like this.

Let’s continue building now. Extrude the back face to form the trunk, I chose to make this a good deal shorter than the hood.

Alright! The body is done, let’s add some wheels!

Building the wheels.

The wheels are just going to be simple cylinders scaled to look like wheels. With this in mind we need to make the wheel wells on our car body. A good place to put them would be on these vertices:

[Advanced concept art, requires years of experience]

Before we do that though we need to do something that will make since in a moment. Grab the faces on one side of side of the car (preferably in the negative X axis)

and drag them, with ProGrids enabled, to the center so that we only have half of a car.

If it looks a little strewed just move the other faces so that it matches.

We want all of our faces to lie near the origin so, if your’s was like mine, box select all of the faces (by just clicking in empty space and dragging) and drag it so that it lies directly on one side of the origin.

Now let’s start making the wheel wells. This part isn’t as intuitive so follow along as best you can. We need to grab the faces on either side of this vertex (shift click)

and subdivide them. Subdividing a face or edge just basically splits the edge or face into more edges or faces. To subdivide these faces, click “Subdivide Faces” in the ProBuilder window. Then we need to grab the center vertices and drag them into a arch-like shape. Reposition the adjacent vertices so that the match this shape.

Now we can grab these faces and extrude them into the car’s body. Select both faces and click the plus icon on the “Extrude Faces” button, this allows us to specify how far we want these faces to extrude. Set the Distance to be -0.25 and then click “Extrude Faces”. Now all we have to do is delete the bottom faces and we are done with this wheel well.

 

Now we can just duplicate this process for the back wheel well.

Now we can just click the plus icon next to Mirror Objects, select X axis, check Duplicate, and click “Mirror”.

This will make a complete and flipped version of our car body that we can drag to one side join to the other one by shift-clicking and pressing Merge Objects.

The last thing we have to do with our car is to add our wheels. These are just cylinders, scaled down on the Y axis, rotated 90 degrees,

and of course, duplicated four times.

Whew! Our car is now done, on to the race track.

Constructing the race track, curves

Making the race track is a little different than making the car. With the race track we will be using curves instead of a mesh. Go to Tools -> ProBuilder -> Editors -> New Bezier Shape.

Let’s select all of our car parts and disable them so that we can see what we are doing.

From here we need to have ProGrids turned on. Click add point and drag it out two units;

don’t forget to make sure the curve is snapped to the grid as well, we’ve done this before by just dragging the entire object in either direction.

Next, click Close Loop, this will gives a rough outline for a track. Now place a point (by clicking on the loop) somewhere across from the point we created and dragged one unit. This will make the “track” look like this:

 

Next, we need to reposition the gray tabs on this new point so they match (and so that the “track” looks like a track) the opposite point. Tweak the gray tabs so that the track is symmetrical.

So there is our track!

Constructing the race track, meshes

Before we start making this curve looking like a race track, let’s change the “Radius”, “Rows”, and “Columns” to make this track easier to edit. Set “Radius” to 1.1, “Rows” to 20, and “Columns” to 30.

There is just one thing we have to do before we can start editing the actual mesh of the curve and that is to just disable “Editing Bezier Shape”.

 

Now we can start manipulating the faces and vertices of our curve. Grab all of the faces on the inside loop by selecting one face and then clicking “Select Face Loop”.

This part of the track will be the road. Switch over to the scale tool and scale it out on the X axis. Then drag these faces down (ProGrids must be disabled!) so that it forms a ledge like this,

Next, scale it out on the Z axis so that we now have space in the turn for a vehicle.

Now, grab the top edges of the faces we just selected (by selecting one and then clicking Select Edge Loop) and scale them out on the Z axis as well so that our faces on the turn are more angular than before.

Also, I decided to lower these edges in order to make the steepness of the turn less prominent, feel free to do this as well if you like. Obviously, we wanted to make a race track that had a sloped turn so if you would like more slope to your turn, feel free to change the vertices to match the look your going for. The last thing I am going to do to our track is to make it more open, as it is now we have some edges hanging over the track. I am just going to select this loop and scale it back a little bit.

There we go, much better.

Testing out our track

The reason race tracks are sloped is so that the cars wont have to turn as much. Let’s see if our sloped track works with a controllable car. Go to Assets -> Import Package -> Vehicles,

we only need the car so un-check anything related to aircraft.

Drag the Car prefab into the scene. You may have to scale your track (not the car!) in order for the vehicle to fit. Re-position the camera and parent it to the car.

Hit play and start testing. If you encounter problems with the track (such as the turn not being steep enough or too steep) just go back and change the mesh. I didn’t get my track right the first round of testing. After you have tested your track out and you like how it looks and drives, congratulations because you are done!

Conclusion

Obviously this tutorial is nowhere near exhaustive. The main purpose of this tutorial was not to show you how to make a car or a race track, but to get you familiar with ProBuilder and modeling in Unity. It should be very apparent that ProBuilder is a very useful tool, with it we can make complex environments, like the race track (even though it isn’t very “complex”), and then modify the environment immediately to better the gameplay. With this fact in mind, start creating and

Keep making great games!


How to create a Turn-Based RPG in Phaser 3 – Part 2

$
0
0

In Part One of this tutorial we created the world scene together with a player moving on it. Now we are going to make the battle scene, where the player units will fight the enemies.

Learning Goals

  • Scene management in Phaser 3
  • Processing keyboard input to navigate through the user interface
  • Using custom events
  • Inheriting Phaser 3 classes
  • Creating basic Battle Scene logic
  • Use timers

Source code

You can download the files for tutorial here.

Assets

All assets used in this tutorial are CC0 licensed. You can download them from here:
Player characters – https://opengameart.org/content/rpg-character-sprites
Enemies – https://opengameart.org/content/dragon-1

Creating the Scenes

We will start with an empty game and later on we will merge it with the code from part one. Two Scenes will do all the work – BattleScene, where the players will fight and UIScene for the interface.

var BootScene = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function BootScene ()
    {
        Phaser.Scene.call(this, { key: 'BootScene' });
    },

    preload: function ()
    {
        // load resources
        this.load.spritesheet('player', 'assets/RPG_assets.png', { frameWidth: 16, frameHeight: 16 });
        this.load.image('dragonblue', 'assets/dragonblue.png');
        this.load.image('dragonorrange', 'assets/dragonorrange.png');
    },

    create: function ()
    {
        this.scene.start('BattleScene');
    }
});

var BattleScene = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function BattleScene ()
    {
        Phaser.Scene.call(this, { key: 'BattleScene' });
    },
    create: function ()
    {
        // Run UI Scene at the same time
        this.scene.launch('UIScene');
    }
});

var UIScene = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function UIScene ()
    {
        Phaser.Scene.call(this, { key: 'UIScene' });
    },

    create: function ()
    {    
        
    }
});

var config = {
    type: Phaser.AUTO,
    parent: 'content',
    width: 320,
    height: 240,
    zoom: 2,
    pixelArt: true,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 0 }
        }
    },
    scene: [ BootScene, BattleScene, UIScene ]
};

var game = new Phaser.Game(config);

In the above code, the most interesting part is in the BattleScene create method. Here we don’t use scene.start, but scene.launch to run the UIScene.
When you run the game now, you won’t see anything special, but keep in mind that both scenes are active at the same time. To visualize that better I will add a graphics object to UI Scene and will draw a simple background for the interface.

First add this row to BattleScene create method:

this.cameras.main.setBackgroundColor('rgba(0, 200, 0, 0.5)');

This is a simple trick to make the scene background green without adding an actual image for it. Now add this code to the UIScene create method:

this.graphics = this.add.graphics();
        this.graphics.lineStyle(1, 0xffffff);
        this.graphics.fillStyle(0x031f4c, 1);        
        this.graphics.strokeRect(2, 150, 90, 100);
        this.graphics.fillRect(2, 150, 90, 100);
        this.graphics.strokeRect(95, 150, 90, 100);
        this.graphics.fillRect(95, 150, 90, 100);
        this.graphics.strokeRect(188, 150, 130, 100);
        this.graphics.fillRect(188, 150, 130, 100);

When you run the game now, you should see the green background of the BattleScene and the three blue rectangles of the UIScene:

Now we need to create a concept for the units – both enemies and player heroes. I will create the base class Unit like this:
Add this code somewhere outside the Scenes code, for example at the top of the project:

var Unit = new Phaser.Class({
    Extends: Phaser.GameObjects.Sprite,

    initialize:

    function Unit(scene, x, y, texture, frame, type, hp, damage) {
        Phaser.GameObjects.Sprite.call(this, scene, x, y, texture, frame)
        this.type = type;
        this.maxHp = this.hp = hp;
        this.damage = damage; // default damage                
    },
    attack: function(target) {
        target.takeDamage(this.damage);      
    },
    takeDamage: function(damage) {
        this.hp -= damage;        
    }
});

And now we will create the Enemy like this:

var Enemy = new Phaser.Class({
    Extends: Unit,

    initialize:
    function Enemy(scene, x, y, texture, frame, type, hp, damage) {
        Unit.call(this, scene, x, y, texture, frame, type, hp, damage);
    }
});

And the Player:

var PlayerCharacter = new Phaser.Class({
    Extends: Unit,

    initialize:
    function PlayerCharacter(scene, x, y, texture, frame, type, hp, damage) {
        Unit.call(this, scene, x, y, texture, frame, type, hp, damage);
        // flip the image so I don't have to edit it manually
        this.flipX = true;
        
        this.setScale(2);
    }
});

As I am a bit lazy, I will use this spritesheet without the characters looking left. To make them turn left in game I will use the property flipX of Phaser3 Sprite.

For our first battle I will hardcode both the player heroes and the enemy dragons. In the next part of this tutorial we will create them according to the game flow.
Change the BattleScene create method to this:

create: function ()
    {
        // change the background to green
        this.cameras.main.setBackgroundColor('rgba(0, 200, 0, 0.5)');
        
        // player character - warrior
        var warrior = new PlayerCharacter(this, 250, 50, 'player', 1, 'Warrior', 100, 20);        
        this.add.existing(warrior);
        
        // player character - mage
        var mage = new PlayerCharacter(this, 250, 100, 'player', 4, 'Mage', 80, 8);
        this.add.existing(mage);            
        
        var dragonblue = new Enemy(this, 50, 50, 'dragonblue', null, 'Dragon', 50, 3);
        this.add.existing(dragonblue);
        
        var dragonOrange = new Enemy(this, 50, 100, 'dragonorrange', null,'Dragon2', 50, 3);
        this.add.existing(dragonOrange);
        
        // array with heroes
        this.heroes = [ warrior, mage ];
        // array with enemies
        this.enemies = [ dragonblue, dragonOrange ];
        // array with both parties, who will attack
        this.units = this.heroes.concat(this.enemies);
        
        // Run UI Scene at the same time
        this.scene.launch('UIScene');
    }

Now when you run the game, you should see something like this:

Its time to add the user interface. We will have three menus – Heroes Menu, Enemies Menu and Actions Menu. All of them will inherit common Menu class. The Menu class will be a container for MenuItem objects and I will use Phaser.GameObjects.Container as its base class.

Lets start with the MenuItem class. It will extend Phaser.GameObjects.Text andit will have only two methods – select and deselect. The first one will turn the text yellow and the second will return it to white.

var MenuItem = new Phaser.Class({
    Extends: Phaser.GameObjects.Text,
    
    initialize:
            
    function MenuItem(x, y, text, scene) {
        Phaser.GameObjects.Text.call(this, scene, x, y, text, { color: '#ffffff', align: 'left', fontSize: 15});
    },
    
    select: function() {
        this.setColor('#f8ff38');
    },
    
    deselect: function() {
        this.setColor('#ffffff');
    }
    
});

Now we need to create the Menu class. It will be a bit more complex. It needs methods to be selected and deselected as a whole (for example when the player need to choose an enemy to attack, the whole Enemies menu is selected). It also needs methods to add menu items.

var Menu = new Phaser.Class({
    Extends: Phaser.GameObjects.Container,
    
    initialize:
            
    function Menu(x, y, scene, heroes) {
        Phaser.GameObjects.Container.call(this, scene, x, y);
        this.menuItems = [];
        this.menuItemIndex = 0;
        this.heroes = heroes;
        this.x = x;
        this.y = y;
    },     
    addMenuItem: function(unit) {
        var menuItem = new MenuItem(0, this.menuItems.length * 20, unit, this.scene);
        this.menuItems.push(menuItem);
        this.add(menuItem);        
    },            
    moveSelectionUp: function() {
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex--;
        if(this.menuItemIndex < 0)
            this.menuItemIndex = this.menuItems.length - 1;
        this.menuItems[this.menuItemIndex].select();
    },
    moveSelectionDown: function() {
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex++;
        if(this.menuItemIndex >= this.menuItems.length)
            this.menuItemIndex = 0;
        this.menuItems[this.menuItemIndex].select();
    },
    // select the menu as a whole and an element with index from it
    select: function(index) {
        if(!index)
            index = 0;
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex = index;
        this.menuItems[this.menuItemIndex].select();
    },
    // deselect this menu
    deselect: function() {        
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex = 0;
    },
    confirm: function() {
        // wen the player confirms his slection, do the action
    }   
});

Now we will create all separate menus:

var HeroesMenu = new Phaser.Class({
    Extends: Menu,
    
    initialize:
            
    function HeroesMenu(x, y, scene) {
        Menu.call(this, x, y, scene);                    
    }
});

var ActionsMenu = new Phaser.Class({
    Extends: Menu,
    
    initialize:
            
    function ActionsMenu(x, y, scene) {
        Menu.call(this, x, y, scene);   
        this.addMenuItem('Attack');
    },
    confirm: function() {
        // do something when the player selects an action
    }
    
});

var EnemiesMenu = new Phaser.Class({
    Extends: Menu,
    
    initialize:
            
    function EnemiesMenu(x, y, scene) {
        Menu.call(this, x, y, scene);        
    },       
    confirm: function() {        
        // do something when the player selects an enemy
    }
});

And now we need to add the menus to the UIScene. Add this code at the bottom of the UIScene create method:

// basic container to hold all menus
        this.menus = this.add.container();
                
        this.heroesMenu = new HeroesMenu(195, 153, this);           
        this.actionsMenu = new ActionsMenu(100, 153, this);            
        this.enemiesMenu = new EnemiesMenu(8, 153, this);   
        
        // the currently selected menu 
        this.currentMenu = this.actionsMenu;
        
        // add menus to the container
        this.menus.add(this.heroesMenu);
        this.menus.add(this.actionsMenu);
        this.menus.add(this.enemiesMenu);

Now you will see that only the actions menu has something in it (because we hardcoded the action Attack). HeroesMenu and EnemiesMenu both are empty. We need to get the data for them from the BattleScene. To access the BattleScene from the UIScene we need to add the following code to its create method:

this.battleScene = this.scene.get('BattleScene');

First I will change the Menu. I will add functionality to clear all MenuItems from it and then add new. First metthod will be called clear and will remove all menu items from the menuItems array. The second will receive an array of units and will add them as MenuItems through addMenuItem. Add this two methods to the Menu class:

clear: function() {
        for(var i = 0; i < this.menuItems.length; i++) {
            this.menuItems[i].destroy();
        }
        this.menuItems.length = 0;
        this.menuItemIndex = 0;
    },
    remap: function(units) {
        this.clear();        
        for(var i = 0; i < units.length; i++) {
            var unit = units[i];
            this.addMenuItem(unit.type);
        }
    }

And we need methods to call this functions for the menus. Add this code to the UIScene:

remapHeroes: function() {
        var heroes = this.battleScene.heroes;
        this.heroesMenu.remap(heroes);
    },
    remapEnemies: function() {
        var enemies = this.battleScene.enemies;
        this.enemiesMenu.remap(enemies);
    },

And we need to call this functions. Add this at the end of the UIScene create method:

this.remapHeroes();
        this.remapEnemies();

Now your game should look like this:

But our game is way too idle. We need to make it move. The next thing on our list is to handle the user input. A player will move through the menu with the arrow keys and will select an item on the menu by pressing space.
To listen for keyboard events, add this row at the bottom of UIScene create method:

this.input.keyboard.on('keydown', this.onKeyInput, this);

And now we need to add onKeyInput to UIScene:

onKeyInput: function(event) {
        
    },

We will have an active menu, and all commands will be executed on it (currentMenu). So lets write the body of onKeyInput like this:

onKeyInput: function(event) {
        if(this.currentMenu) {
            if(event.code === "ArrowUp") {
                this.currentMenu.moveSelectionUp();
            } else if(event.code === "ArrowDown") {
                this.currentMenu.moveSelectionDown();
            } else if(event.code === "ArrowRight" || event.code === "Shift") {

            } else if(event.code === "Space" || event.code === "ArrowLeft") {
                this.currentMenu.confirm();
            } 
        }
    },

Its time to implement the turns. For now we will use an array with all units in the BattleScene. We will keep the index of the currently active unit and if it is a player, it will wait on user input, else the game will pick random player hero and the enemy will attack it.

Add this row at the end of BattleScene create method:

this.index = -1;

This index will show us the currently active unit in the units array. Now we need the nextTurn function. Add this code to BattleScene:

nextTurn: function() {
        this.index++;
        // if there are no more units, we start again from the first one
        if(this.index >= this.units.length) {
            this.index = 0;
        }
        if(this.units[this.index]) {
            // if its player hero
            if(this.units[this.index] instanceof PlayerCharacter) {                
                this.events.emit('PlayerSelect', this.index);
            } else { // else if its enemy unit
                // pick random hero
                var r = Math.floor(Math.random() * this.heroes.length);
                // call the enemy's attack function 
                this.units[this.index].attack(this.heroes[r]);  
                // add timer for the next turn, so will have smooth gameplay
                this.time.addEvent({ delay: 3000, callback: this.nextTurn, callbackScope: this });
            }
        }
    },

Here we send the custom event “PlayerSelect”. We will wait for it in UIScene.
The other interesting part here is after the enemy’s unit turn we use a timed event to call nextTurn with 3 seconds delay. This way we will have the time to see what is going on on the screen.

In Phaser3 you can listen for events from one Scene on another Scene. Add this row at the bottom of UIScene create method to listen for ‘PlayerSelect’:

this.battleScene.events.on("PlayerSelect", this.onPlayerSelect, this);

And then we need to create UIScene onPlayerSelect method.

onPlayerSelect: function(id) {
        this.heroesMenu.select(id);
        this.actionsMenu.select(0);
        this.currentMenu = this.actionsMenu;
    },

Its relatively simple logic, we select the id-th element from the heroesMenu. Then we select the first element in the actionsMenu and it becomes the currently active menu. Now we need to add confirm methods to the menus. The user will interact with the actions menu first, then he will confirm his selection with spacebar and then he should choose an enemy to perform the action (attack) on. When an enemy is selected, we need to inform the BattleScene.

Now change the ActionsMenu to this:

var ActionsMenu = new Phaser.Class({
    Extends: Menu,
    
    initialize:
            
    function ActionsMenu(x, y, scene) {
        Menu.call(this, x, y, scene);   
        this.addMenuItem('Attack');
    },
    confirm: function() {      
        this.scene.events.emit('SelectEnemies');        
    }
    
});

On confirm we will send custom event ‘SelectEnemies’. Now add this to the end of the UIScene create method:

this.events.on("SelectEnemies", this.onSelectEnemies, this);

And now we need to create the UIScene onSelectEnemies method:

onSelectEnemies: function() {
        this.currentMenu = this.enemiesMenu;
        this.enemiesMenu.select(0);
    },

Its relatively simple, we just make the enemiesMenu active and we select the first enemy.

Now change the confirm method of EnemiesMenu to this:

confirm: function() {        
        this.scene.events.emit("Enemy", this.menuItemIndex);
    }

And then add this row at the bottom of UIScene create method:

this.events.on("Enemy", this.onEnemy, this);

And the onEnemy method will deselect all menus and then will send data to the BattleScene:

onEnemy: function(index) {
        this.heroesMenu.deselect();
        this.actionsMenu.deselect();
        this.enemiesMenu.deselect();
        this.currentMenu = null;
        this.battleScene.receivePlayerSelection('attack', index);
    },

We are almost ready. We will start the first turn from the UIScene create method. This way we will have both scenes ready before starting the fight. Add this row to the bottom of UIScene create method:

this.battleScene.nextTurn();

You can play a bit with the game and you can see that the player selection is not received by the BattleScene.
Add this method to BattleScene:

receivePlayerSelection: function(action, target) {
        if(action == 'attack') {            
            this.units[this.index].attack(this.enemies[target]);              
        }
        this.time.addEvent({ delay: 3000, callback: this.nextTurn, callbackScope: this });        
    },

Here we get an action and a target. We use the currently active unit to attack the target. Then we used a timer event to call the next turn.
Now the game is playable but it needs messages to inform the player what is going on. Here is a simple Message class, that you can use:

var Message = new Phaser.Class({

    Extends: Phaser.GameObjects.Container,

    initialize:
    function Message(scene, events) {
        Phaser.GameObjects.Container.call(this, scene, 160, 30);
        var graphics = this.scene.add.graphics();
        this.add(graphics);
        graphics.lineStyle(1, 0xffffff, 0.8);
        graphics.fillStyle(0x031f4c, 0.3);        
        graphics.strokeRect(-90, -15, 180, 30);
        graphics.fillRect(-90, -15, 180, 30);
        this.text = new Phaser.GameObjects.Text(scene, 0, 0, "", { color: '#ffffff', align: 'center', fontSize: 13, wordWrap: { width: 160, useAdvancedWrap: true }});
        this.add(this.text);
        this.text.setOrigin(0.5);        
        events.on("Message", this.showMessage, this);
        this.visible = false;
    },
    showMessage: function(text) {
        this.text.setText(text);
        this.visible = true;
        if(this.hideEvent)
            this.hideEvent.remove(false);
        this.hideEvent = this.scene.time.addEvent({ delay: 2000, callback: this.hideMessage, callbackScope: this });
    },
    hideMessage: function() {
        this.hideEvent = null;
        this.visible = false;
    }
});

I will add the message object to the UIScene as it is part of the interface. Add this to the UIScene create method:

this.message = new Message(this, this.battleScene.events);
        this.add.existing(this.message);

And with this our second part of the tutorial is finished.

Supercharging Materials with the Scriptable Render Pipeline in Unity

$
0
0

Introduction

In this tutorial, we will be using the Scriptable Render Pipeline in Unity to supercharge our materials and make them look awesome. We will be utilizing the Scriptable Render Pipeline (or, as it is often called, SRP) in two different applications. In the first, we will be making a more realistic material for glass, and the second we will be adding some visual “effects” to a tele-porter to make it look more “futuristic”. By the end of this tutorial you should have a fair grasp of the Scriptable Render Pipeline and how to use it in your own projects.

What is SRP?

The Scriptable Render Pipeline came out with Unity 2018. The bottom line is this, SRP is a visual, node based, way of creating and editing materials, without having to write a single line of code. Because of this we can create some pretty cool looking results in an extremely short time. The initial concept and look of the shader graphs from SRP may look daunting and complicated, but once you get the hang of it, you’ll find it is an incredible tool.

Assets and requirements

You can download the assets used in this tutorial by clicking here. These assets are from the Kenny assets website. You can check out their website here, kenny.nl. For this tutorial you will need a computer (in case you didn’t figure that out) and that computer needs to be running at least Unity 2018.1, this is the only version that SRP works in. Also, this tutorial assumes that you have no prior knowledge of SRP but that you have a basic knowledge of Unity (how to navigate the UI, manipulate tabs, etc.). Also, previous experience with a node based editor (either from using the Blender compositor or something similar) will make thing a lot easier to understand. With that out of the way, let’s start into our project!

Setting up the project

Open up Unity and click “new”. Give your project a sensible title, something like “SRPDemo”. In order for SRP to work we need to change the template from “3D” to “Lightweight RP” (RP stands for Render Pipeline).

Click “Create Project” and let’s get started!

Project 1: Making glass

When Unity finishes compiling our project, you’ll see that we have an example scene already loaded. Go ahead and hit play just to see what the team at Unity has created for a demo. We aren’t going to be using this actually so go to your project tab and in the folder marked “scenes” create a new scene called “Glass and Portal”.

Open this up and let’s get started. First things first, we need to import our assets. If you downloaded them and still haven’t unzipped it, just set the extraction folder to be the “Assets” folder in our project.

This reduces an extra coping stage. Next, we need to import SRP. Go to Window -> and Package Manager

and import or update “Scriptable Render Pipeline”.

Once it has finished importing, go to the materials folder in your project tab and create a new material called “glass”.

Then right-click and go to Create -> Shader -> PBR Graph

and name it “glassShader”.

After that, go to our glass material and set the Shader to our “glassShader”.

Now if we double click on our glassShader it will bring up a new window.

This is where all the magic happens. If you like you can leave this window free from the main Unity window, or you can find a good place to dock it, I put mine here.

A quick glance at this window shows that most of the attributes on the main node, are those that were in the original material window.

This is how SRP works, we edit these basic attributes by using other nodes in order to change the look of an object. With this in mind, let’s get started making the first part of our project, a glass material. The tricky thing about glass is it needs to be transparent but reflective as well. There are several ways to do this, the way we are going to do it is by using something called a “Fresnel” (pronounced “FRA-nel”) effect. Fresnel is the natural phenomena where the visible edges of an object are lighter or shinier than the center. It is most easily visible on spherical objects. The way we are using Fresnel is by having the edges reflect light while being transparent in the center. But we need an object in the scene with glass so that we can see the result of our shader in realtime. I used the space ship from the asset pack. Drag our glass material onto the windshield of the space craft and we will be ready to start making our shader.

Go to our shader window and right-click in empty space. Go to “Create Node”.

Just search “Fresnel” and it should come up.

Then plug the out thread into the “Alpha” input on our base node.

Click “Save asset” and check out what it looks like. Well, it hasn’t changed.

How come? Well, there are a couple ways of interacting with this base node, you can see the options at the bottom of the node in “Workflow” and “Surface”.

Workflow should probably remain “Metalic” if we are working with a man made object, all “Metalic” means is that the surface is more prone to reflecting. For this project we should change “Surface” from “Opaque” to “Transparent”.

Now hit save and check out the results. Now you can tweak the power of the Fresnel effect and change the “Albedo” color to give an interesting tint.

Get creative!

Project 1: making the portal

That one was kind of simple right? Well I hope you learned as much as you can because this one isn’t going to be as simple. In this part we are going to be making the portal. Find the “portal” game object in your project tab and drag it into the scene.

Go ahead and disable the space ship so that we can have a clean work space. Go to the materials folder and create a new material called “Portal” and create a new PBR graph called “Portal Shader”.

 

Then set up the material with the shader and drag it on to the portal. Open up the shader and dock it next to the old shader.

Now this part of the tutorial will use something called “Procedural noise”. Procedural Noise, or “clouds” as they are sometimes called, is like the Swiss Army knife of shaders, it has a lot of uses. And I think you will see why as we start using them. What we are doing to this portal is to change the look of center of the portal (the actual teleporting part) so that it looks more like an effective futuristic portal, not a solid object.

Let’s start this off by creating a new noise node by just searching “noise”,

select the “Simple Noise” node and connect it to the albedo field.

Saving the shader reveals an extremely fine noise on the game object; let’s change the scale so that it isn’t as fine.

This is the key stone of our shader. Right now it is plugged into the albedo field which means that we can’t change the color of this node and that it will always be this dull grey color. To fix this, we need another node that will blend the texture and a color that we specify. This node is appropriately called a blend node. You know how to add it in (right-click and click “create node” and search “blend”), drag the out put of the noise node and place it in one of the slots in the blend node. Then create a new color node (you know the drill) and place it’s output in one of the slots on the blend node. Change the color of the color node and drag the out put of the blend node into the albedo socket.

Now that we have the texture colored, we want it to be bright and emit light. This can be accomplished by simple dragging the output of blend node into the “Emission” socket on the base node.

But this seems a little too bright. What we can do about this is to create another node, called a Multiply node, that simply multiplies two values together, except, we can make one of the values an output from another node, which, in our case, is the Blend node. You already know how to create a node so just search Multiply and add it in. The bottom value is what we use to change the emission.

It has four values because the output from the blend node is a Vector4, I encourage you to play around with these values as you can make some really interesting results, for me, I just wanted the brightness to be decreased as a whole which meant that I reduced all of the values to a fourth of the original.

By the way, be sure to save the asset after you make a change, otherwise you wont be able to see the result! Our portal is looking fairly decent, but it lacks something. If we were to put this in a game it would just look like a very odd colored game object. What could we add to make it look, well, look more like a portal? How about animating this texture? How about a texture that will change its appearance over time? This would give the game object a sense of urgency or usefulness. There are a couple ways to do this, one would be to just create a new node, called a Time node that outputs a value that gets modified over time, and then to plug that node into the Scale socket on the noise texture. Go ahead and try this, just pick an output socket (or experiment) and plug it into the Scale socket on the Simple Noise node.

This does make the texture change over time, but we can also make it so that it looks like the texture is constantly moving in one direction on the game object. Basically, we can make the texture move. To do this we need a new node (of course!) called a Tiling and Offset node. By now whenever I say that we need a new node and I specify the name, you should know that all you have to do is search the name and you can find it and add it to your shader. This node’s output will plug into the UV section of our texture, and the input we are concerned with on this node is the “Offset” socket, this will receive input from the top socket on the Time node.

 

This should make your preview automatically start moving. What is going on here? Well, whenever we create a noise node we are actually creating a new texture. And when we use the UV socket we are modifying the properties of the texture itself. One of those properties for a noise texture is the position, or offset, of the texture. And so we are telling the texture to change its position over time (by using the time node). Now if we hit save and take a look, our texture is moving!

However, if your are looking at this and thinking, “That’s kind of slow” you are thinking the exact same thing when I first saw this. And if you are thinking that (and you’ve been paying attention), you should already have a solution. How would you fix that? Simple, use a Multiply node! That’s right, all you do is place it between the Time node and the Tiling and Offset node and change the lower value to speed it up! You knew how to do that right?

Wow. Guess what you’re done!

Properties

Before we finish, there is one part of the SRP that is kinda like the icing on the cake, properties. Click the plus icon on the left window and create a Vector1

called “Scroll Speed” and set the default to 15.

 

Now actually drag the property onto the shader and grab the output and plug it into the lower value on the Multiply node.

What did we just do? Well, go to you material and check out what just happened.

Isn’t that cool! Now you can customize this value during gameplay without having to open the shader at all!

Conclusion

I hope you have enjoyed this tutorial. This tutorial was obviously not exhaustive, there is much more to explore, which is what I encourage you to do. Start exploring what other nodes can go into the UV slot, or experiment with different types of noise textures. It’s really a neat tool, so use it to,

Keep making great games!

Student Success! Creating Games with Yone Moreno Jimenez

$
0
0

Hey everyone! Recently, we released a special collection of courses – the Discover Unity Game Development curriculum, which was included as part of the Humble Unity Bundle

Yone Moreno Jimenez was one of many students who grabbed this bundle, and he has already worked through several of the courses, and published his creations for the world to see. 

Yone, it’s great to meet you! Could you tell us a little about your background in programming?

Hello. I’ve been programming as part of my Computer Science studies, however I’ve been curious about game development since 2007.

What courses in our Discover Unity Game Development curriculum interested you the most?

The most interesting course for me was Master Unity Game Development – Ultimate Beginner’s Course, because I learned how important vectors and coordinate systems are when creating games.

It was fascinating when the instructor, Pablo Farias Navarro, implemented a system that only allowed the player to jump if it was grounded. He did this by casting a ray from each corner of the player’s box, and he explained the calculations for this in a very friendly way.

Additionally, I enjoyed the course because in each lesson, there are small challenges where you get to put the knowledge into practice, instead of just copying and pasting the content.

I published my version of the 3D Multi-Level Platformer game – ColorPlatform – after making my own modifications to the main screen, the game over screen, and the background music.

It’s fantastic to hear that you were not only able to create the game, but to make it yours! Since completing your platformer, have you worked through any of the other courses in the bundle? 

I’ve also taken the 2D game development courses, and have learned a lot with them. The instructor Glauco Pires does a great job – he works at a good pace, and explains the the concepts behind why he does each step.

So far, I’ve created and published Pong , Plane in the River , and also Space Attackerson the Itch.io game publishing platform.

You’ve taken a wonderfully active approach to your learning! What are your development goals – is game development something that you are interested in pursuing as a career, or is it more of a hobby that you are passionate about? 

So far, I’ve created some games with other artists. Unlucky Charlie is a game centered around following the rhythm of a heartbeat.

Friendly Fire Fly is a co-operative side scroller, where you need to avoid colliding with fire and the enemies.

When it comes to a career, I’m still figuring things out. Although I like the creativity involved with programming, I’m hesitant about coding with only text (as is the case with web development), so now I’m investing one year in learning Unity. I also plan to study game design and development at University next year.

I’m also really interested about using programming to help people with disabilities, like the blind, because I think that the time invested in programming, developing and designing is especially worth it when helping other people. This was inspired from a weekend where I helped to develop an audio game for the blind – you can read all about my experiences doing this here.

That’s a fantastic goal – it’s great that you’re inspired to make games accessible to everyone. Before we leave you, is there any advice that you’d like to offer to other learners and aspiring game developers like yourself? 

Learning to develop a game is interesting and entertaining, but don’t just learn by yourself – join up with at least one other person who shares your interests in development. Try to find projects where you can collaborate, and learn how to work as a team.

Thanks Yone, for that great advice, and for inviting us all to see what game development is like for you. 

The Humble Unity Bundle contains lots of great Unity games, courses and assets – it’s only available for a limited time, but you can still grab your copy here.

Catch you all next time… 

Phaser Progressive Web Apps Tutorial – How to Create Offline-First Games

$
0
0

Phaser is a fantastic HTML5 game framework, which can easily be run on any device. The downside is since it is an HTML5 framework, there is no out-of-the-box solution to create a mobile app version of your game. You can use other tools like PhoneGap, CocoonJS, Ionic, etc. to create a hybrid mobile app, but you are dependent on third party tools. However, if you optimize your game properly, anyone can play your game on a mobile device, but it requires the user to visit your site, and it doesn’t have some of the nice app features, like an icon on your home screen, or playing the game when you’re offline.

What if you could have a hybrid app that is a mixture of a native mobile app and a web app? Progressive Web Apps, or PWAs, feel like a native mobile app, can work offline, are responsive, and are very easy to install. Additionally, we can make our game a PWA without any third party tools. We just need to add a few files to our project, and we need to add some additional code to our main html page.

You can download all of the files associated with the source code here.

Tutorial Requirements

For this tutorial, you will need the following:

  • Basic to intermediate JavaScript skills
  • A code editor
  • A local web server
  • Chrome Web Browser

For this tutorial, it is recommended that you are familiar with basic Phaser concepts such as scenes, setting up the config for your game, running your game locally, etc. If you are not familiar with these concepts, you will be able to follow along with the tutorial, but we will not be covering these topics in depth. If you would like to learn more about these concepts or would like a refresher, you can check out the How to Create a Game with Phaser 3 tutorial here on GameDev Academy.

Project Setup

For this tutorial, we are going to reuse some of the code from the Phaser 3 webpack project template that is available on GitHub. We won’t be using webpack for this tutorial, but we will be using the base template and Phaser logo image that is in the repo, along with a few additional images and a style sheet. You can download the base project code here: Project Template

In the zip folder, you will see three folders (css, js, and img) and an index.html file. If you open index.html in your code editor, you should see the following code:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="css/style.css" />
    </head>
    <body>
        <script src="https://cdn.jsdelivr.net/gh/photonstorm/phaser@3.10.1/dist/phaser.min.js"></script>
        <script src="js/game.js"></script>
    </body>
</html>

In the js folder, there is a file called game.js which has all of the logic for our Phaser game. If you open,game.js you will see the following code:

var config = {
  type: Phaser.AUTO,
  parent: 'phaser-example',
  width: window.innerWidth,
  height: window.innerHeight,
  scene: {
    preload: preload,
    create: create
  }
};

var game = new Phaser.Game(config);

function preload() {
  this.load.image('logo', 'img/logo.png');
}

function create() {
  this.logo = this.add.image(0, 0, 'logo');
  this.logo.setScale(0.5);

  Phaser.Display.Align.In.Center(
    this.logo,
    this.add.zone(window.innerWidth/2, window.innerHeight/2, window.innerWidth, window.innerHeight)
  );

  this.tweens.add({
    targets: this.logo,
    y: 450,
    duration: 2000,
    ease: 'Power2',
    yoyo: true,
    loop: -1
  });
}

Lastly, if you start your server and try running your game, you should see a black screen with the Phaser logo.

Adding a service worker

Now that our project is set up, we now work on adding a service worker to our game. Before we start coding, let’s review what a service worker is. A service worker is a JavaScript worker or web worker that is essentially a JavaScript file that does not run in the main browsers thread and it can be used to intercept requests, cache and retrieve cached resources, and it can deliver push notifications. What does this mean for us? We can use the service worker to cache our game assets on the user’s browser, which will allow us to use the cached assets for faster loading times and we can use them to allow the player to play our game offline.

In order to use service workers, your game must be hosted over HTTPS (you can use localhost when testing). Lastly, most browsers support service workers, but not all of them do. You can track which browsers are supported here: Service Worker Ready.

With that out of the way, let’s start adding our service worker. To install a service worker, the first thing we need to do is register our service worker. In your project folder create a new file called sw.js and place this at the root of your project. For now, we will leave this file empty. Next, open index.html and add the following code above the other JavaScript files:

<script>
    if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
            navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            }, function(err) {
                console.log('ServiceWorker registration failed: ', err);
            });
        });
    }
</script>

Let’s review the code we just added:

  • First, we check if the service worker API is available.
  • If the API is available, then once the page has loaded we register the service worker at /sw.js and we set the scope for our service worker to be /. The scope parameter is used to control which parts of your application can be used by the service worker. For example, if you set the scope to be /games/ then the service worker will only be able to access files under the /games/ path.
  • Lastly, we log some information about the service worker.

Now, if you save and reload your game in the browser if you open the console inside Chrome’s developer tools, you should see a message about the service worker registration being successful.

You can also validate that the service worker was installed by clicking on the Application tab in Chrome’s developer tools. From there, if you click on the Service Workers tab you will see the service worker we registered.

Cacheing assets

Even though our service worker is installed, it is not doing anything. To fix this, we will update our service worker to cache all of the assets that are used by our game. In sw.js, add the following code:

var cacheName = 'phaser-v1';
var filesToCache = [
  '/',
  '/index.html',
  '/img/logo.png',
  '/img/icon-192.png',
  '/img/icon-256.png',
  '/img/icon-512.png',
  '/js/game.js',
  '/css/style.css',
  'https://cdn.jsdelivr.net/gh/photonstorm/phaser@3.10.1/dist/phaser.min.js'
];

self.addEventListener('install', function(event) {
  console.log('sw install');
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('sw caching files');
      return cache.addAll(filesToCache);
    }).catch(function(err) {
      console.log(err);
    })
  );
});

Let’s review the code we just added:

  • First, we declared two variables cacheName and filesToCache. The cacheName variable is used to store the name of the cache that we will be using to store the cached version of our files. The filesToCache variable is an array of files that we want to cache for our game.
  • Next, we added an event listener for the service worker install event and we provided a callback function that will run when the install event is triggered.
  • In the callback function, we called the event.waitUntil() method, which takes a promise as an argument and it uses it to know if the installation was successful.
  • In the event.waitUntil method, we first call the caches.open() method, which is used to open the cache in the user’s browser. This method takes the name of the cache you want to open, cacheName, and it returns a promise that will resolve to the cache object that is stored in the user’s browser.
  • Finally, we call the addAll() method on the cache object that was returned. This method takes the array of URLs that we want to be cached, filesToCache, and it returns a promise that will resolve with void if all of the files are cached. One important thing to note is if any of the files are fail to download into the cache, then the whole install step will fail.

This may be a lot to digest, but basically, in the install callback, we did the following:

  • We opened the cache.
  • We cached our list of files.
  • We confirmed if the files were cached or not.

With our logic for caching in place, we need to add the logic that will allow us to use the cached assets. To use the cached assets, we need to add a new event listener for the fetch event, which is triggered any time a user visits a page after the service worker has been installed. In sw.js add the following code at the bottom of the file:

self.addEventListener('fetch', (event) => {
  console.log('sw fetch');
  console.log(event.request.url);
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    }).catch(function (error) {
      console.log(error);
    })
  );
});

In the code above we are doing the following:

  • We added an event listener for the fetch event for our service worker. This event is triggered anytime a request is made that is within the scope of our service worker.
  • When we receive the event we called the event.respondWith() method. This method with prevent the browser’s default and will use the promise we provide instead.
  • In the event.respondWith() method, we first called the caches.match() method and we passed it the event.request object. Then, if the requested resource was found in the cache we return the cached resource. If the requested resource was not found in the cache, we fetch that request and return the response.

Testing in Chrome

Now that we have the code for loading our cached assets in place, we can test our game offline to make sure the cached assets are being loaded properly. If you save and reload your game, it will probably look like nothing has changed, and if you look in Console in the developer tools, you might not see any logs for the fetch event. The reason for this is because of how the service worker is actually updated.

When the browser detects a change in the service worker file, it will install the new service worker in the background. When this happens, you old service worker is still controlling the current page you are on, and it will enter a waiting state. Once you close the current page, or navigate to a different site, then the old service worker will be destroyed and the new one will take control.

To see this in Chrome, if you switch back to the Application tab, and click on Service Workers, you should see the currently active service worker, and the updated one.


No, if you close the tab your game is running in, and you reload your game in another tab the service worker should show it has been updated.

To get around this, you can check the Update on reload checkbox at the top of the service workers tab. When this is checked, Chrome will automatically install the updated service worker for us.

Now, if you look in the Console tab, you should see some fetch events being logged.

To see if our game will work when we are offline, we can either shut down the server that is rendering our game, or back in the service workers tab, we can click the Offline checkbox. Now, if you try reloading your game, you should see that the service worker is loading the assets for our game from the cache, and that our game is playable offline.

Cache management

If you ever want to force the service worker to use a new version of the cache, or if decide to use multiple caches, you will need a way to clean up the old caches on the users device. To do this, we can add an event listener for the activate event. This event is triggered any time a service worker takes control, and we will use this event to clean up our cache.

In sw.js, add the following code at the bottom of the file:

self.addEventListener('activate', function(event) {
  console.log('sw activate');
  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (key !== cacheName) {
          console.log('sw removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
  );
});

Let’s review the code we just added:

  • First, we added an event listener for the activate event, and we passed it a callback function that will run when this event is triggered.
  • In this callback function, we use the caches.key() method to get all of the current caches for our service worker.
  • We then loop through all of these keys and we delete them if they are not equal to our current cacheName variable.

Adding to our home screen

With our game now working offline, we will start adding the functionality for allowing our game to be added to the users home screen. To include this functionality, we will need to listen to the beforeinstallprompt event that Chrome will fire when certain conditions are meet. When this event is fired, we can show a button to have the user install our game, and when they click this button, it will show Chrome’s prompt to add the app to their home screen.


Chrome used to automatically show the prompt to add the PWA to the user’s home screen, however starting in Chrome 68, Chrome no longer does this automatically and you have to listen for the beforeinstallprompt.

In order for Chrome to fire the beforeinstallprompt your PWA will need to meet the following criteria:

  • The web app is not already installed.
  • The user must have been on the site for 30 seconds.
  • The web app has a manifest file that includes the following:
    • short_name or name
    • icons must include 192px and 512px sized icons
    • start_url
    • display must be one of: fullscreen, standalone, minimal-ui.
  • Has to be served over HTTPS.
  • Has a registered service worker that has a fetch event handler.

Let’s start adding the code for this to our game. The first thing we will do is create the manifest file. The web app manifest file is a simple JSON file that tells the browser about the your web application and how it should behave when it is installed on the user’s device. In your project, create a new file called manifest.json, and add the following code to it:

{
    "name": "Phaser PWA",
    "short_name": "Phaser",
    "description": "Phaser template Web App.",
    "start_url": "/",
    "background_color": "#000000",
    "theme_color": "#0f4a73",
    "display": "standalone",
    "icons": [{
      "src": "img/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },{
      "src": "img/icon-256.png",
      "sizes": "256x256",
      "type": "image/png"
    },{
      "src": "img/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }]
  }

Let’s review the code we just added:

  • The name and short_name properties are the names that are shown to the user on their home screen and in the prompt that is shown to the user when they install the app.
  • The icons property is an array of icons that will be used for the app icon in the home screen and the app launcher.
  • The start_url tells the browser where the web app should start when it is launched.
  • The display property is used to customize the browser UI that is shown when the app is launched. You can read more about the different options here.
  • The theme_color property is used to control the color of the task bar.
  • The background property is the color that is used on the web app splash screen.

With the code for the manifest file in place, we just need to update our index.html file. Open index.html and replace all of the code in the file with the following code:

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta charset="utf-8">
        <meta name="theme-color" content="black" />
        <link rel="manifest" href="manifest.json" />
        <link rel="icon" href="img/icon-192.png" sizes="192x192" />
        <link rel="icon" href="img/icon-256.png" sizes="256x256" />
        <link rel="icon" href="img/icon-512.png" sizes="512x512" />
        <link rel="stylesheet" href="css/style.css" />
    </head>
    <body>
        <!-- The Modal -->
        <div id="myModal" class="modal">
            <!-- Modal content -->
            <div class="modal-content">
                <span class="close">&times;</span>
                <p>Add to home screen?</p>
                <button onclick="offlinePrompt()">Install</button>
            </div>
        </div>
        <script>
            if ('serviceWorker' in navigator) {
                window.addEventListener('load', function() {
                    navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
                        console.log('ServiceWorker registration successful with scope: ', registration.scope);
                    }, function(err) {
                        console.log('ServiceWorker registration failed: ', err);
                    });
                });
            }
            let deferredPrompt;
            window.addEventListener('beforeinstallprompt', function (e) {
                console.log('beforeinstallprompt triggered');
                e.preventDefault();
                deferredPrompt = e;
                modal.style.display = 'block';
            });
            // Get the modal
            var modal = document.getElementById('myModal');
            // Get the <span> element that closes the modal
            var span = document.getElementsByClassName('close')[0];
            // When the user clicks anywhere outside of the modal, close it
            window.onclick = function(event) {
                if (event.target == modal) {
                    modal.style.display = 'none';
                }
            }
            // When the user clicks on <span> (x), close the modal
            span.onclick = function() {
                modal.style.display = 'none';
            }
            function offlinePrompt() {
                deferredPrompt.prompt();
            }
        </script>
        <script src="https://cdn.jsdelivr.net/gh/photonstorm/phaser@3.10.1/dist/phaser.min.js"></script>
        <script src="js/game.js"></script>
    </body>
</html>

In the code above we did the following:

  • In the <head> section of our code, we added a meta tag to make our game responsive. We also added a link to our manifest file, and we added links to our icons.
  • In the <body> section of our code, we added code for a modal in our game. This modal contains an install button, and when it is clicked it will show Chrome’s prompt to install the PWA. By default, we hide our modal and we only show it when the beforeinstallprompt is fired.
  • Lastly, in the <script> section of our code, we added an event listener for the beforeinstallprompt event, and in the callback function we prevent the default event from happening, we save that event for accessing later, and lastly we display our modal. When the install button in the modal is clicked, we call the prompt() method on that event.

Now, if you save and reload your game, you can test the add to home screen functionality.

When you are developing your PWA, you can manually trigger the beforeinstallprompt event. To do this, you will need to enable the #enable-desktop-pwas flag in Chrome, and you will need to be on Chrome OS 67 or later. To enable this flag, you can visit chrome://flags/#enable-desktop-pwas and then change the dropdown to Enabled.


Once this is done, in the Application tab of the developer tools, if you click on the Manifest tab, you should see a Add to homescreen link, which when clicked it will manually trigger the beforeinstallprompt event.


With the code for the manifest file in place, our PWA is complete. You now have an offline first Phaser game that is a PWA.

Conclusion

I hoped you enjoyed this tutorial and found it helpful. If you have any questions, or suggestions on what we should cover next, let us know in the comments below.

Student Success! How Lucas Knight stepped out of the box when designing his latest game… and found Emojis!

$
0
0

Hey everyone! We’re back with another story of student success – this time, with Lucas Knight, who took a creative approach to game design with his latest game, Emoji Pet.

Lucas, it’s great to have you here for an interview! To get things rolling, can you tell us a little about how you got into game development? 

So I think I first got a taste for game development playing a 2D MMORPG called Graal Online as a kid (early 2000s). The game was actually way ahead of its time. It was really a game development platform that allowed players to develop their own content and essentially create their own online games. I loved the experience of being involved in the creative process and developed a real passion for pixel art and game development.

After graduating from university (in a totally unrelated field) I decided to do the responsible thing and try to create a browser-based MMORPG🙄. We eventually did get a functional prototype online but the project ultimately fell apart because of a personal disagreement between myself and the coder I was working with.

Since then I’ve been learning how to code myself. I had pre-ordered Zenva’s HTML5 Game Development Mini-Degree while enrolled in a web development boot camp here in Toronto. The course conveniently arrived a few days after the boot camp ended and so I took the opportunity to jump into Phaser 3.

That sounds like perfect timing – it’s great to see how proactive you’ve been in teaching yourself to code. We were super excited to see your take on the Virtual Pet Game, and how Zenva helped to get you there. Can you tell us more about the courses that you used and how they helped you to create Emoji Pet?

 Yeah I wanted to learn Phaser 3 because I’m really interested in browser games.  Phaser 3 is just being released now so I thought it was a good time to get in.  Since it is so new (and currently incomplete) the documentation isn’t great.  I wanted to find a comprehensive tutorial series and found Zenva’s HTML5 Game Development Mini-Degree.  I think the third project you make in the course is a Virtual Pet.  I wanted to see if I could put my own twist on it and make a virtual pet game using nothing but emoji text characters.

So what was your experience like using the Phaser 3 Framework? 

 It’s really quick and easy to get a game scene up and running in Phaser.  I had never used it before but still found it intuitive and easy to work with.  I can see that there are some really powerful tools in there but the real drawback at the moment is the documentation.

That you managed the framework so easily is very evident in your game – it’s so slick that the only difficulty that players experience is the choice over whether to feed their Emoji Pet salad or ice cream! Was creating the game all ice cream and hamburgers, or were there some challenges that you had to tackle?

So I wanted to work with Emojis mainly for the novelty of it.  I hadn’t heard of anyone using emoji as game sprites before and I wanted to see if it could be done.  This presented a challenge as I had difficulty finding any relevant information.  Thanks to Zenva and the great Phaser community out there I got through it.  I managed to make animations for the emoji character by switching between various emoji characters.  For example the emoji will throw up if you feed it too much junk food 😔 > 🤢 > 🤮. It was easy to transform them just like any other sprite.  I even managed to make a background with a scaled-up cityscape emoji 🌁.

I think my main takeaways are:

1.   Using emoji instead of normal sprites is possible.

2.   Emojis render differently on different devices, which is sort of interesting but ultimately probably a drawback.

3.   Since I don’t have to provide the user with a bunch of image sprites, the game should load more quickly.

All that perseverance definitely paid off! It’s fascinating to hear how versatile you managed to make the Emojis in your game. Now that Emoji Pet is complete, do you have any new projects in the works?

 I’m playing with the idea of a 2D zombie survival side scroller … made with emoji of course🧟♂.

Thanks so much for joining us for an interview! Before we say goodbye, do you have any words of wisdom for other Zenva students who are having their first crack at game development?

The Phaser community is really great.  If you need support check out the HTML5 Game Devs message board as well as the Phaser Slack Channel.

A huge thanks to Lucas for joining us, and for all the tips and tricks he’s given us! Catch you all next time…

Get 😁 🤣 😍 by playing Emoji Pet here

 

JS13KGames Tutorial Series

$
0
0

Welcome to our js13kgames video series! In these tutorials you will learn to create a HTML5 game from scratch using the Kontra micro-framework. What’s fantastic about this game is that it’s less than 13KB in total!

The js13kgames competition is a free online event where people from all over the world build games that are less than 13KB in size.

Zenva is one of the supporters of this initiative and we believe is a great way to put in practice what you’ve learned in our video courses, and to make something simple and fun!

Source Code

The source code for all the tutorials can be downloaded here

Tutorials

 

The Complete Programming and Full-Stack Bundle – 20 Course Smart Curriculum

$
0
0

🔥🏆 Go from beginner to full-stack developer!

The Complete Programming and Full-Stack Bundle is the world’s most effective way to go from beginner to professional coder. Whether your goal is to advance your career, start your own business or expand your existing skill-set, our 20-course Smart Curriculum has something in store for you.

This bundle is suitable both for absolute beginners and more advanced developers. Projects cover a wide range of different topics including:

  • Crafting interactive websites and web applications with Bootstrap, Angular, React, Node and Express
  • Coding in Python and building smart Machine Learning and AI applications
  • Building games with Unity, Phaser and JavaScript
  • Creating stunning Virtual Reality games and applications
  • Game artwork creation with Blender and Gimp

Access The Complete Programming and Full-Stack Bundle on Zenva Academy


Creating a Phaser 3 Template – Part 1

$
0
0

The goal of this tutorial is to help you create a Phaser template that you will be able to reuse and extend in any future project you work on. The benefit of having a template is that it is a good starting point for your game, and it can save you a lot of time when you are setting up your game.

This template will include the following scenes:

  • Boot – the first scene that is loaded by Phaser, and it will load the assets that are required in the preloader scene.
  • Preloader – displays a logo, a progress bar, and loads all of the assets that are needed in the game.
  • Title – the title screen of our game, and it displays buttons to start the game, view credits, and modify options.
  • Options – will contain any settings you want players to be able to modify in your game (example: mute audio).
  • Credits – credits for your game.
  • Game – contains the main logic for your game.

Source Code

You can download all of the files associated with the source code for part one here.

Tutorial Requirements

For this tutorial, you will need the following:

  • Basic to intermediate JavaScript skills
  • A code editor
  • Chrome Web Browser
  • Have NodeJS installed locally

For this tutorial, it is recommended that you are familiar with basic Phaser concepts such as scenes, setting up the config for your game, running your game locally, etc. If you are not familiar with these concepts, you will be able to follow along with the tutorial, but we will not be covering these topics in depth. If you would like to learn more about these concepts or would like a refresher, you can check out the How to Create a Game with Phaser 3 tutorial here on GameDev Academy.

Also, for this tutorial, it is recommended that you are familiar with Git and NodeJS, but it is a not a requirement for this tutorial. If you are not familiar with these concepts, you will still be able to follow along.

Project Setup

For this tutorial, we are going to use the Phaser 3 Webpack Project Template as a starting point for our template, and it can be found here on GitHub. You can obtain a copy of this template in one of two ways: 1) Use Git to clone the repository locally, or 2) You can download a zipped version of the repository from GitHub.

To download the repository, visit the GitHub link and click on the green ‘Clone or download’ button, and you should see the following:

Then, click on the ‘Download ZIP’ button and extract the contents of the folder.

To clone the repository, copy the URL that is shown, and in your terminal or command prompt, run the following command:

git clone https://github.com/photonstorm/phaser3-project-template.git

(Note: For the rest of this tutorial, I will just be using the word terminal.)

After you get a copy of the template files, you will need to install the required Node dependencies to get the template to run. In your terminal, navigate to the template folder, and run the following command: 

npm install
 . This will take a few seconds to run, and once it is done, you should see a message that a number of packages was added to your project.


To make sure everything installed correctly, run the command: 

npm run start
 , and you should see a message that states webpack was compiled successfully. Now, if you open your browser and visit: http://localhost:8000/ , you should see that your Phaser game is running.

 


With the basic project setup, we can start creating our template.

Update Project and Create Scenes

The first thing we are going to do is take all of the logic from the index.js file and split it into multiple files. We are going to take the game logic and the Phaser config, and put those components in their own files. In your project folder, go into the src folder and create two new folders: Config and Scenes. In the Config folder, create a new file called config.js and add the following code to it:

import 'phaser';

export default {
  type: Phaser.AUTO,
  parent: 'phaser-example',
  width: 800,
  height: 600
};

Then, in the Scenes folder, create a new file called GameScene.js and add the following code to it:

import 'phaser';

export default class GameScene extends Phaser.Scene {
  constructor () {
    super('Game');
  }

  preload () {
    // load images
    this.load.image('logo', 'assets/logo.png');
  }

  create () {
    this.add.image(400, 300, 'logo');
  }
};

Most of this code should look similar to the code that was in the original index.js file, but let’s review what we just added:

  • export default is part of the ES6 module system, and it allows for us to export a single function, class, or primitive from a file and it allows us to load that content in another file.
  • extends is used to create a class that is a child of another class.
  • super is used to access and call functions on the parent’s object. When super is called, it calls the parent class’s constructor.
  • In the config.js file, we exported an object that has all of the configuration settings that will be used in our Phaser game.
  • In the GameScene.js file, we exported a class that extends the Phaser.Scene class and it currently contains logic for displaying an image in our game.

Now, to use our new config object and GameScene class we will need to import them in our index.js file and make a few other updates. Open index.js and replace all of the code in the file with the following code:

import 'phaser';
import config from './Config/config';
import GameScene from './Scenes/GameScene';

class Game extends Phaser.Game {
  constructor () {
    super(config);
    this.scene.add('Game', GameScene);
    this.scene.start('Game');
  }
}

window.game = new Game();

In the code above, we did the following:

  • We imported the config object and the GameScene class from the files we just created.
  • We created a new class called Game and we set it to extend the Phaser.Game class.
  • In the super method, we passed in the config object to the Phaser.Game class’s constructor.
  • Since our config object did not have any information about the scenes of our game, we had to add them to our Phaser game.
  • Lastly, we told Phaser to start our Game scene.

Now, if you go back to your browser, you should see the Phaser logo and it should not be moving since we removed the logic for tween.


Next, we will create the rest of the scenes that will be used in our template. For each scene we will create a new class that does nothing for the time being. In your project folder create the following files: BootScene.js, CreditsScene.js, OptionsScene.js, PreloaderScene.js, and TitleScene.js.

In BootScene.js, add the following code:

import 'phaser';

export default class BootScene extends Phaser.Scene {
  constructor () {
    super('Boot');
  }

  preload () {
  }

  create () {
  }
};

In PreloaderScene.js, add the following code:

import 'phaser';

export default class PreloaderScene extends Phaser.Scene {
  constructor () {
    super('Preloader');
  }

  preload () {
  }

  create () {
  }
};

In TitleScene.js, add the following code:

import 'phaser';

export default class TitleScene extends Phaser.Scene {
  constructor () {
    super('Title');
  }

  preload () {
  }

  create () {
  }
};

In OptionsScene.js, add the following code:

import 'phaser';

export default class OptionsScene extends Phaser.Scene {
  constructor () {
    super('Options');
  }

  preload () {
  }

  create () {
  }
};

In CreditsScene.js, add the following code:

import 'phaser';

export default class CreditsScene extends Phaser.Scene {
  constructor () {
    super('Credits');
  }

  preload () {
  }

  create () {
  }
};

Finally, we need to import these new scenes into index.js and add them to our game. Open index.js and replace the all of the code in the file with the following code:

import 'phaser';
import config from './Config/config';
import GameScene from './Scenes/GameScene';
import BootScene from './Scenes/BootScene';
import PreloaderScene from './Scenes/PreloaderScene';
import TitleScene from './Scenes/TitleScene';
import OptionsScene from './Scenes/OptionsScene';
import CreditsScene from './Scenes/CreditsScene';

class Game extends Phaser.Game {
  constructor () {
    super(config);
    this.scene.add('Boot', BootScene);
    this.scene.add('Preloader', PreloaderScene);
    this.scene.add('Title', TitleScene);
    this.scene.add('Options', OptionsScene);
    this.scene.add('Credits', CreditsScene);
    this.scene.add('Game', GameScene);
    this.scene.start('Game');
  }
}

window.game = new Game();

Boot Scene

With all of our scenes in place, we will start building out the rest of our template. The first scene we will focus on is the Boot Scene. This scene will be the first scene that is loaded by our game, and we will use it load any assets that will be displayed in the Preloader Scene.

For our template, the only asset we need for the Preloader Scene is a logo image. To follow along with the tutorial, you can use the following image: Zenva Logo

If you have your own logo image, you are welcome to use it. Once you have your image, save it in the assets folder.

In BootScene.js, update the preload and create functions to match the following code:

preload () {
  this.load.image('logo', 'assets/zenva_logo.png');
}

create () {
  this.scene.start('Preloader');
}

In the code above, we loaded in our logo image and once it is loaded we transition to the Preloader scene. Note: If you used your own image, make sure you update zenva_logo.png with the name of your logo image.

Preloader Scene

With the Boot Scene in place, we can now focus on the Preloader Scene. In this scene, we are going to display the logo we loaded in the Boot Scene, add a progress bar, and finally load the rest of the assets we will be using in our template.

For the progress bar, we are going to reuse some of the code from the Creating a Preloading Screen in Phaser 3 tutorial. For the purpose of this tutorial, we will not be covering this code in depth, but if you would like to learn more about it we recommend that you check out the tutorial mentioned above.

In PreloaderScene.js, update the preload function to match the following code:

preload () {
  // add logo image
  this.add.image(400, 200, 'logo');

  // display progress bar
  var progressBar = this.add.graphics();
  var progressBox = this.add.graphics();
  progressBox.fillStyle(0x222222, 0.8);
  progressBox.fillRect(240, 270, 320, 50);

  var width = this.cameras.main.width;
  var height = this.cameras.main.height;
  var loadingText = this.make.text({
    x: width / 2,
    y: height / 2 - 50,
    text: 'Loading...',
    style: {
      font: '20px monospace',
      fill: '#ffffff'
    }
  });
  loadingText.setOrigin(0.5, 0.5);

  var percentText = this.make.text({
    x: width / 2,
    y: height / 2 - 5,
    text: '0%',
    style: {
      font: '18px monospace',
      fill: '#ffffff'
    }
  });
  percentText.setOrigin(0.5, 0.5);

  var assetText = this.make.text({
    x: width / 2,
    y: height / 2 + 50,
    text: '',
    style: {
      font: '18px monospace',
      fill: '#ffffff'
    }
  });
  assetText.setOrigin(0.5, 0.5);

  // update progress bar
  this.load.on('progress', function (value) {
    percentText.setText(parseInt(value * 100) + '%');
    progressBar.clear();
    progressBar.fillStyle(0xffffff, 1);
    progressBar.fillRect(250, 280, 300 * value, 30);
  });

  // update file progress text
  this.load.on('fileprogress', function (file) {
    assetText.setText('Loading asset: ' + file.key);
  });

  // remove progress bar when complete
  this.load.on('complete', function () {
    progressBar.destroy();
    progressBox.destroy();
    loadingText.destroy();
    percentText.destroy();
    assetText.destroy();
  });

  // load assets needed in our game
  this.load.image('blueButton1', 'assets/ui/blue_button02.png');
  this.load.image('blueButton2', 'assets/ui/blue_button03.png');
  this.load.image('phaserLogo', 'assets/logo.png');
}

Let’s review the code we just added:

  • We displayed our logo image in our scene.
  • We then used Phaser’s graphics and text options to create a progress bar that also displays the percentage.  We also included a Loading... message, and we display which file is currently being loaded.
  • We listen for the progress event, and use it to update the progress bar and the progress bar percentage.
  • We listen for the fileprogress event, and use it to update the file text.
  • We listen for the complete event, and use that to clean up all of the progress bar game objects.
  • Finally, we load the rest of the assets we will be using in our game.

The last thing we need to add to our Preloader Scene is logic to transition to the Title Scene. This logic could be put in with the rest of the logic that is triggered in the complete event, however, when there are not that many assets the progress bar will disappear quickly, and the user may not see the logo image at all. To work around this, we can either move the logo to its own scene or we can add some additional logic to our scene. For the purpose of this tutorial, we will do the latter.

To work around this issue, we will add a new time event to our Preloader Scene. We will use this event to make sure the logo is displayed for a minimum number of seconds, and when the event is triggered it will call a new function that will be used to load the Title Scene. Lastly, we will also call this new function when the complete event is fired that way we know that all assets have been loaded and the user has had enough time to view our logo.

In PreloaderScene.js, update the bottom of the preload function code to match the following:

// remove progress bar when complete
this.load.on('complete', function () {
  progressBar.destroy();
  progressBox.destroy();
  loadingText.destroy();
  percentText.destroy();
  assetText.destroy();
  this.ready();
}.bind(this));

this.timedEvent = this.time.delayedCall(3000, this.ready, [], this);

// load assets needed in our game
this.load.image('blueButton1', 'assets/ui/blue_button02.png');
this.load.image('blueButton2', 'assets/ui/blue_button03.png');
this.load.image('phaserLogo', 'assets/logo.png');

Then, add the following functions to PreloaderScene.js:

init () {
  this.readyCount = 0;
}

ready () {
  this.readyCount++;
  if (this.readyCount === 2) {
    this.scene.start('Title');
  }
}

In the code above, we did the following:

  • Updated the function that is ran when the complete event is fired to call the new ready function.
  • Added a new timed event that is triggered after 3 seconds. When the event is triggered, it also calls the new ready function.
  • We added Phasers Scene’s init function that is triggered when a scene first starts. We use this function to create a new variable called readyCount and we set it to 0.
  • Lastly, we added a new function called ready, and when it is called we increment the readyCount variable. Once this variable is equal to 2, then we know it is safe to transition to the Title Scene.

Conclusion

With the logic for our Preloader Scene in place, this brings part one of this tutorial to an end. In Part Two, we will wrap up our tutorial by:

  • Adding the logic for the Title Scene.
  • Adding the logic for the Options Scene.
  • Adding the logic for the Credits Scene.
  • Adding the logic for the Game Scene.

I hoped you enjoyed part one of this tutorial and found it helpful. If you have any questions, or suggestions on what we should cover next, let us know in the comments below.

The Unity Game Development Mini-Degree

$
0
0

Go from Zero to Professional Game Programmer as you build 2D, 3D, Mobile, Virtual Reality and Augmented Reality Games with Unity!

The Unity Game Development Mini-Degree is a massive collection of online courses that will teach you how to code and build impressive games with Unity. Beginning with the fundamentals of coding and the basics of the Unity Engine, you’ll progress through to more advanced concepts such as as procedural level generation, immersive cut scenes, and RPG mechanics.

Through these 30 comprehensive courses, you’ll create a rock-solid professional portfolio that can propel your career forward, and help you to create the games you’ve always dreamed of.

Access this Mini-Degree on Zenva Academy

Virtual Reality Mini-Degree

$
0
0

Become a professional VR developer as you learn to code and create 15 immersive games in Unity from the ground up – no prior programming experience required! 

Welcome to the world’s most comprehensive online curriculum on VR game development, where you will learn and master the foundations of C#, Unity and VR by building practical projects. Whether your goal is to make VR games for fun, start a business in this exciting field, or become a professional Unity development, this online curriculum contains everything you need to reach your goals.

Covering all the major VR platforms – including Oculus Rift, HTC Vive / SteamVR-compatible headsets, Samsung Gear VR, and Google Cardboard (Android and iOS), you will learn:

  • How to code in C# and build immersive games with Unity
  • Locomotion in VR
  • Working with 360° media (photos and video)
  • Preventing simulator sickness
  • 3D game mechanics and physics
  • Room-Scale Experiences with SteamVR and VRTK
  • Modular level design techniques
  • Basics of 3D game artwork creation using Blender and MagicaVoxel

…and more!

Access this Mini-Degree on Zenva Academy

Hey! Listen: Sound Design with Unity & FMOD – Part 1

$
0
0

Series Overview

In the first part of this sound design tutorial series we will be learning how to integrate FMOD Studio with Unity and setup a few basic audio events using only the scripts and features that FMOD provides. In the rest of the series we’ll be looking at calling and tweaking FMOD events via code, doing some cool stuff with occlusion and physics sounds using filters and parameter changes at run-time. We will also be looking at adding the Google Resonance plugin, creating super-realistic audio environments for use with AR & VR games and applications.

How does this all help you? Well for starters, FMOD Studio integration makes it really easy to setup most of the audio logic you’ll ever need in your games with just a few clicks! For a little bit of time spent setting up a Unity project with FMOD, you will save heaps in the long run. And it’s all free!

Now, I’m writing this tutorial series with the assumption that you are familiar with the Unity editor because there’s a few things to learn if you’ve never tried FMOD. Also, I’d like to touch on a bit of theory as well as practical skills. First up, let’s talk about why sound design is worth studying and how we can get better before we start on the technical stuff.

Sound Design Inspirations

Where can you gather sound design inspiration? YouTube let’s plays. Really. I usually only use YouTube to access discussions on game design theory but I don’t think anything has helped me with sound design as much as blind gamer Terry Garret playing The Legend of Zelda: Ocarina of Time (OoT). Truly-inspirational stuff, not just for sound design considerations! It’s no wonder OoT is so adored for it’s ‘rich, atmospheric environments’ there’s a lot of sounds in the 3D world that Terry can use to gather information. I know this game is quite old and there are many better examples for complex sound in modern 3D games (such as Overwatch) but for such a huge topic it’s best to nail the core basics first.

Watch a bit of the embedded video below, one of Terry’s let’s plays, and think about this: which sound events are helpful and why?

OK that was a trick question, all of the sounds are helpful! When it comes to sound design for accessibility, more is more! But the ‘why’ does help us to remember the ‘how’ that we will be covering in detail ahead.

We should be designing for accessibility always and in multiple passes. Can we play with just the sound? Can we play with just the image? If you ask yourself these questions, your game won’t just be more accessible, it will be that much more immersive and involved for the many that can access both sight and sound. Your game world is only as rich as either the audio or the visuals, so if you want a good review for your latest hard work, make sure you’re giving love to more than one of the player’s senses!

So I’ve watched quite a few of Terry’s OoT let’s plays but I’ll attempt to make a rough list of the most important sound design considerations that I think could be gathered by studying any one of them. I’m going to describe these considerations under the following headings: ambiance, persistence and feedback.

Ambiance

Ambiance describes the particular qualities given to a sound by the space in which that sound exists. In this example I am including all the audible ways that the environment reacts to the player character’s presence and actions. Footstep sound changes, wall collisions and familiar audio effects such as reverb heard in empty spaces are all examples of ambiance. In OoT, Terry determines where he is by the sound his character’s footsteps are making, he knows he’s hit a wall when those footsteps slow down, and he knows what the wall’s made of by hitting it with his sword. Doors make a certain sound when struck and this sound tells Terry that he’s within striking distance but he might just need to realign to access it.

Persistence

The persistence of a looping audio clip might be considered ‘ambiance’ per the more-common use of the word, and it kind of is… But the use of persistent audio in accessible games is super-important so it should get it’s own heading! The right amount of persistent and distinguishable sound effects can enable sight-impaired players to navigate effectively without relying on cues that they’ve run into walls. Persistence is vital for hazards or enemies to gauge their location and potential movements. In OoT Terry also uses the persistent sounds of; a torch by one of the doors in a dungeon room, the humming from an interactive crystal object, and the trickle of water that marks the river running through Hyrule field to navigate through the game world.

Feedback

The bare minimum sound category required for a user, with feedback, more is more. There are so many examples here and much of it is this: if you press any button, you should know what it has done (or not done) without seeing the screen. Feedback communicates affordances. In OoT, feedback tells Terry that he is near enough to interact with a sign or a character, that he can target an enemy or interactable and know which one it is (thanks to Navi’s alerts). Again, you can never have too many feedback sound effects! If something happens or, in some cases, doesn’t happen then it should have a sound effect!

These above headings will help us understand and categorise the FMOD components we will be integrating with Unity and the logic for our sound events within FMOD Studio.

Setting Up FMOD Studio & Unity

To integrate FMOD into a Unity project we will need two things, FMOD Studio and the FMOD Unity integration package. To access the download page you just need to make a free account at with FMOD.

After you’ve installed FMOD Studio and downloaded the FMOD Unity integration package let’s setup Unity. In an existing or new Unity project, go to Assets > Import Package > Custom Package… In the popup window, navigate to where you saved the FMOD Unity integration package and open it.

Once that package has been imported you should see the below errors in your Unity console. If you ever get this error in the future, you may have just changed some file folder structures. We just need to point Unity towards an FMOD Studio Project folder!

So lets just open FMOD Studio, make a new project and then save it by going to File > Save As. I recommend saving it in the Unity project that this Studio project will be linked to so that the project folders are not split up accidentally in the future.

IMPORTANT! Saving your FMOD Studio Project is important, but none of the logic you implement there will be seen in Unity unless you hit Build! This builds all the logic and sound into something that applications like Unity can access easily.

Now that we have imported the integration package into a Unity project and we’ve created an FMOD Studio project we can point Unity to the FMOD Studio file in that project folder. In Unity, Click FMOD > Edit Settings and you should see the FMODStudioSettings data in the Unity Inspector panel. It should look like the screenshot below. If you don’t see the FMOD button at the top of your Unity window then you may have forgotten to import the FMOD Unity integration package (see above).

Next click the Browse button next to the Studio Project Path, navigate to your FMOD Studio project folder and open the FMOD Project File.

The Inspector should look as follows.

Also take note that there is a new folder called “StreamingAssets” in your Unity project folder. This folder now contains the file that your game build will access at runtime, it contains all the sound and logic from FMOD Studio. If you lose it you can just rebuild your FMOD project.

Now we’re all setup! If all’s gone well you can now hit Clear in your Unity Console panel and you shouldn’t see anymore path reference errors pop up.

The Game & Assets

In this tutorial we will be setting up a little indoors level to be played in first-person perspective. The setting will be a large office building and we’ll be using the persistent sounds of the office equipment, collisions and footstep changes to help our players navigate. The player will need to find a specific computer in the office and interact with it. I know this seems pretty boring, but on the plus side you might be able to record all of the sounds you need in your own home. Otherwise I suggest you grab some CC0-licensed sound from freesound.org. Please note that you actually don’t need to import these sounds into your Unity project, it will happen when we make an event in FMOD Studio. Just leave the sounds in your Downloads folder or something. You might also want to grab some textures from opengameart.org to help you distinguish between the different floor types – the footstep sounds I’ve used are for wood and concrete. I’ve made a zip file with the sounds, textures and 3D models that I’ve used in this tutorial – click here to download .

Creating Events With FMOD Studio

There are a few ways to create Events in FMOD Studio and my favourite is the ol’ drag-and-drop. Simply drag an audio file from your operating system’s file explorer into the Events tab in FMOD Studio. Let’s start by adding some persistence to the computers that, along with the desks, will function as obstacles and potential interactives to sort through in search of the correct one. Let’s drag the computer_hum.wav file into Events, leave 3D Event selected in the popup window and click Create.

The above step has done two things: made a new 3d Event and added a new file to your FMOD Studio Audio Bin. We can check out the Audio Bin in FMOD Studio by clicking Window > Audio Bin.

The Assets tab in the Audio Bin shows us all of the working files in this FMOD Studio project folder. Dragging files into either the Events tab or the Audio Bin Assets tab makes a copy of that file in the project folder. You can go ahead and drag all your sounds into the Audio Bin, or you can drag one at a time as we create more Events later.

Our next step is to add some simple logic to our computer_hum event that will suit a persistent sound effect for multiple identical objects in proximity. First though, let’s make this Event loop. Right click the audio track and then click New Loop region. Notice the blue bar that appears above the audio track, this is the Loop Region. You can pull the handles left and right to shorten or extend the Loop Region into or past the full length of the track. I’m just going to leave it looping from the start to end of the track because I don’t want any gaps or cuts and I know this track loops nicely in full.

Next we will address the phasing issue. Phasing happens when we play multiple identical audio tracks at the same time, this causes the volume to increase and we don’t want that. To solve this we are going to add an offset to the start time of this Event and then randomise it. Left click on the audio track to select it, then click on the little white triangle to extend the Trigger Behavior tray at the bottom of the FMOD Studio window. Next adjust the Start Offset dial by left-clicking and dragging your mouse up. Leave it at about 50% – this will make the Event seek to half-way through the audio track when it is called in Unity. Now we add what’s called Modulation to randomise the Start Offset. Hold the Alt key (Option for Mac) on your keyboard and click and drag on the Start Offset dial, notice that a thick green line expands from the pointer on the dial. Lastly, click the Loop icon in the top-right corner of the Trigger Behavior tray to ensure that it loops back to the start of the audio track as soon as it gets to the end. Otherwise we’d have a lot of empty space because of the Start Offset.

So to recap, this random range of Start Offset is applied when the Events are called and *should* be different for each instance if you give a large range. To be safe you could add Modulation to the full range of the Start Offset. If you want you could also add a very tiny Modulation to the Pitch dial to help distinguish separate sounds, but don’t go overboard because pitch is affected by some 3D sound filters and movement. When a persistent sound moves towards you (or vice versa) the pitch increases, the opposite is true when moving away. This is the Doppler effect, and is simulated in most every 3D game these days. It’s setup by default in Unity’s audio system and as long as we setup these FMOD Events in 3D we will get the effect, which helps a player know if a sound is moving towards or away from them.

Almost done, we just have to assign our finished Event with all it’s audio and logic to a Bank. Banks allow us to tweak the properties of a collection of events all at once. We might, for example, want to drop the volume of all the persistent background sound effects while a character is talking – so we would put all the ‘background’ Events into a similarly-named Bank of their own. But for such a small project, just the one Master Bank is fine. Right click computer_hum in the Event tab, click Assign to Bank > Master Bank.

You can check out which Banks reference which Events in the Banks tab.

Level Setup in Unity

Now that we understand how Events work in FMOD Studio we can start creating our actual game in Unity, placing models and calling Events from the scripts that came with our FMOD integration package! Start by quickly blocking out a large office space. I usually start with cubes (right click Hierarchy > 3D Object > Cube) for walls because a cube is 1 by 1 Unity units which I think of as metres. So if you scale it by 4 on the Y axis then you have a 4 metre high wall and a starting reference for the overall size of your level.

For the floor I’ve used two planes, one for the wooden floor and one for concrete.

Another thing that helps be gauge the size of the level is dropping in a character controller and just walking around. We will need the Characters package from Unity Standard Assets. Go to Assets > Import Package > Characters. You may want to uncheck the audio files though because all our audio will be contained in the FMOD folder. If you want to use the default Unity footstep audio you could just drag them into your FMOD Audio Bin folder after unpacking it in Unity.

Drag the RigidBodyFPSController prefab into your scene. If you started with a default Unity scene you will get an error stating that there are 2 audio listeners, just delete the Main Camera that came with the scene and clear that error.

All we need to finish setting up the player controller is to add an FMOD Studio Listener component to the Camera. This is all we need to do, don’t remove the Unity Audio Listener component that was already on the Camera.

If you downloaded my project files zip you should drag the models and textures in now so we can finish setting up the graphics of the level.

Now let’s texture our ground planes. Did you know – If you drag a texture onto a mesh that only has a default material applied to it, Unity will make a new material in a “Material” folder named after that texture. Pretty cool.

Next let’s make one computer and desk setting. If you used the Google Poly models I did, they’ll probably be huge in your scene. The desk was not too bad so I just scaled it down in the Scene window but the Computer was giant!

Let’s shrink that Computer model. Click on the model object in your Project window and in the Inspector, under the Model tab, you’ll want to change the Scale Factor to something like 0.01 to have a more manageable size.

Another problem with the Computer model is the origin point, it’s waaaay off the centre of the geometries! These Google Poly models sometimes man… We could fix this in Blender but a quick fix is to select all the child objects of the model’s prefab and use the Move Tool in the Scene view to bring them left on the X axis and forward on the Z axis roughly centered on the origin.

Playing FMOD Events

With a complete desk setup we can add the script for our first FMOD Event. With the Computer parent object selected, click Add Component in the Inspector tab and search for Studio Event Emitter. This is the script that will call Events from FMOD.

For this object, we want the persistent audio to play when the object is enabled – so select that in the Play Event drop down. It also makes sense then to stop the event when the object is disabled. I like to pool objects in my scene by disabling them when they are ‘destroyed’ and re-enabling them whenever I want to spawn a new one. If you like to instantiate and destroy your game objects then use Object Start and Object Destroy to start and stop your events. To search your FMOD project for events, click the little magnifying glass next to the Event text box.

If you don’t see your Event in the search box, you may have forgotten to Build in FMOD Studio after you made it. It’s a good habit just to Build whenever you make changes to Events in FMOD Studio, right after you save – Remember that Build hotkey (F7 on Windows).

Now duplicate the desk setup a couple of times so that we can pick one to be the goal, I made the Computer game object a child of the Desk to make it easier.

Before the next step press play in Unity and have a walk around. Notice how the offset makes identical looping audio tracks sound distinct and seperate?

Now lets setup some feedback for the player when they step in front of the correct computer. I chose a computer startup chime so it’s like the computer wakes up when the player gets near. Let’s go back to FMOD Studio and create a new event. We don’t need any extra logic, just assign it to the Master Bank, save and Build.

Next, let’s attach a Sphere Collider to one of the Computers and make it a trigger. Now add a second Studio Event Emitter for the computer_startup Event and set the Play Event to Trigger Enter and the Collision Tag to Player. Don’t forget to set the tag on the actual Player controller.

Well that’s the basics and the end of Part 1. In Part 2 we will be using custom scripts to change the footsteps sounds when the player is on different surfaces, add occlusion effects and Google Resonance zones for truly immersive spatial sound.

Now that you’re familiar with playing Events why don’t you try adding a couple of extra persistence and ambiance sound effects. Maybe a radio playing on one side of the office and collision sounds when the player walks into the desk.

Till next time!

Getting Started in AR with Poly and Unity

$
0
0

Introduction

Over the past year or so, Unity has become a popular tool for creating AR apps as it makes things relatively simple for developers to get things up-and-running. One obstacle, however, is that many developers aren’t exactly experts at 3D modeling and purchasing assets can be very expensive. Google set out to fix this problem by creating Poly, which is an API for accessing and managing 3D models designed for AR and VR apps. The best part of all is that Poly has its own Unity plugin and a developer can go from zero modeling experience to having a full game populated with assets in a few short clicks. If you are a game developer who is fine at scripting but never actually gets past the “placeholder cubes” phase of your project, Poly is exactly the type of tool that you need for your next game.

The Poly toolkit for Unity has a large library of assets that you can use in your AR or VR projects. You can also upload your own assets to share or to use privately. Poly also has a REST API for dynamically loading in assets at runtime, which opens up a whole world of possibilities when it comes to making games.

Getting Poly and Setting Up A New Unity Project

First, before we get started with Poly in our AR app, we have to download the plugin. Next, let’s create a new Unity project. I am using Vuforia for my AR Camera, and if you do not have it installed, I’d recommend checking out this tutorial first. Using an AR camera isn’t absolutely necessary to use Poly, but all of the 3D assets were designed with AR and VR projects in mind.

In order for the Poly plugin to work properly in Unity, we have to allow “unsafe code” by going to File > Build Settings > Other Settings and ticking the box.

Now let’s go to Assets > Import Package > Custom Package and import the .unitypackage file that we just downloaded. After the package installs, the Poly window should open and we are good to go. Now we can use the Poly API in our Unity Project! If you are just interested in manually importing 3D models into your Unity game, this is all you need. You can access the Poly library by clicking Poly > Browse Assets in your Unity menu. An account or an API key is not required to use Poly for manually selecting 3D models and importing them into your Unity project, just make sure you give proper credit to the model creators in accordance to the CC-BY licence.

Importing Assets at Edit Time

The Poly plugin comes with a nice GUI library to look through. Let’s add a cheeseburger into our view by searching for “burger” and importing it into our project. You will see that Poly’s library contains both assets made by google and assets made by other Poly users.. You can upload your own assets to Poly to share with other developers or to use privately in your own project.

Poly even makes attributing the 3D model artists easy by generating an attributions file. Let’s go to Poly > Update Attributions File and in our assets folder we should be able to find our Attributions.txt file with the proper CC-BY attribution for our cheeseburger.

Importing Assets at Run Time

One of the coolest features of Poly is that we have the ability to load assets dynamically during runtime with simple API requests. This can make a huge difference in how much resources your game uses. This also makes changing assets as easy as changing a line of code, rather than importing new 3D models to your Unity project.

Let’s try and import a new assets during our game’s runtime using the Poly API.

To do this we will first need to get an API key. Let’s head over to the Google Developer Console if you don’t have one already, create a google account and also create a new project for our Unity app. Once created, click on the “Create Credentials” button in the dashboard to get an API key.

Next, we need to paste our API key into the Poly Runtime settings by going to Poly > Poly Toolkit Settings > Runtime. For making requests to the Poly asset library, we only need to create an API key. If you are interested in uploading and accessing private assets, then you will need to create an Oauth Client ID and Client Secret to receive the appropriate tokens.

Now that we have our API key in our project, we can use the Poly toolkit. Another thing we will need to do is enable the Poly Toolkit by going to Assets/PolyToolkit/Prefabs and adding it to our scene. In order to make API requests and load assets at runtime we will need this in every scene that uses Poly.

Now that we are all set to make an API request, let’s start as simple as possible by making a request for one specific 3D model. Open up the Poly window and search for the donut in the Poly API asset library. When you select the model you will see an import location, this location contains the model’s unique ID. This is what we will use for our request.

Let’s create a basic script to make the request to the Poly API using the model’s ID. Create a new C# script by right clicking in the Unity asset browser. For this tutorial I have created a script called loadNewAsset.cs and attached it to my scene as a component.

Inside the loadNewAsset.cs file, let’s create a new request using the donut model’s ID. Make sure to include ‘using PolyToolkit;’ before the class is created. We will pass in the ID as a string. For now we are just going to use the default options for our import.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PolyToolkit;

public class loadNewAsset : MonoBehaviour {

    void Start() {
       PolyToolkit.PolyApi.GetAsset("assets/5ApDrJhdXcG", // ← id
       result => {
           PolyApi.Import(result.Value, PolyImportOptions.Default());
       });
    }
}

 

We should see a delicious-looking sprinkled donut on our screen. If you don’t see it, make sure that you have the PolyToolkit prefab added to your scene and also make sure that your C# script containing the request is included in the scene as well. Since we didn’t actually store our donut anywhere after the request, it will disappear once we stop running the scene and trigger some console warnings, but as you can see, an asset can be loaded into our scene with just a couple of lines of code!

What if we don’t want to go through the trouble of grabbing the ID manually? Well, with the Poly API we can simply make a query to search the asset library for us! Let’s go ahead and change our code so that we can make a request for frosted donuts and import the first three results.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PolyToolkit;

public class loadNewAsset : MonoBehaviour {

     public int assetCount = 0;

     void Start() {
     // Create a new request
           PolyListAssetsRequest req = new PolyListAssetsRequest();
     // Search by keywords    
           req.keywords = "frosted donut";
     // Make the request with a callback function
           PolyApi.ListAssets(req, GetDonuts);
     }
    
     private void GetDonuts(PolyStatusOr<PolyListAssetsResult> result) {
      // Set options for import so the assets aren't crazy sizes
       PolyImportOptions options = PolyImportOptions.Default();
       options.rescalingMode = PolyImportOptions.RescalingMode.FIT;
       options.desiredSize = 2.0f;
       options.recenter = true;
      // List our assets
       List<PolyAsset> assetsInUse = new List<PolyAsset>();
      // Loop through the list and display the first 3
       for (int i = 0; i < Mathf.Min(3, result.Value.assets.Count); i++) {
      // Import our assets into the scene with the ImportDonuts function
         PolyApi.Import(result.Value.assets[i], options, ImportDonuts);
         assetsInUse.Add(result.Value.assets[i]);
       }
     
     }
    
     private void ImportDonuts(PolyAsset asset, PolyStatusOr<PolyImportResult> result) {
        assetCount++;
      // Line the assets up so they don't overlap
       result.Value.gameObject.transform.position = new Vector3(assetCount * 2.5f, 0f, 0f);
     }
}

 

As you can see, we have used import options to make sure that the assets load a certain way. In our code we specified the desired size and position we wanted our assets to load in with. There are more options for filtering results, if you want to be very specific or if you want to limit your assets to a lower or higher polygon count. You can also narrow it down by featured results to ensure that you get relevant models from your request.

Attributions

The CC-BY license requires that proper attribution is given to the original creator and Poly makes this simple by providing both a manual way to generate imported assets and a method for getting the information from the API if you are loading assets dynamically.

To manually update the attributions, simply go to Poly > Update Attributions as mentioned earlier and it will generate a .txt file for you. This is ideal for users who are importing assets from Poly at edit time, and not during the game’s runtime.

To update the attributions during our AR app’s runtime, we can use the information given to us by the API when we make the request.

Let’s go ahead and modify our app to display text on the screen and then update our code to get the attribution info for all of the assets that are importing from the API request. First, add a canvas element to the scene. Next, add some text to the canvas and call it “AttributionsText”. We will leave this blank, as it will be populated by the information provided by our request when the game is run.

Let’s anchor it to the side of our screen and make it big enough to display all of the info, you may need to make the font size smaller or a different color so you can read.

Now let’s update our code to add the AttributionText object to our display.

public class loadNewAsset : MonoBehaviour {

 public int assetCount = 0;
// Reference the text element
 public Text attributionsText;


      void Start() {
            PolyListAssetsRequest req = new PolyListAssetsRequest();
 
            req.keywords = "frosted donut";
            req.pageSize = 10;
            PolyApi.ListAssets(req, GetDonuts);
      }
      
      private void GetDonuts(PolyStatusOr<PolyListAssetsResult> result) {
        PolyImportOptions options = PolyImportOptions.Default();
        options.rescalingMode = PolyImportOptions.RescalingMode.FIT;
        options.desiredSize = 2.0f;
        options.recenter = true;
        List<PolyAsset> assetsInUse = new List<PolyAsset>();
        for (int i = 0; i < Mathf.Min(3, result.Value.assets.Count); i++) {
          PolyApi.Import(result.Value.assets[i], options, ImportDonuts);
          assetsInUse.Add(result.Value.assets[i]);
// Update the Attributions Text
          attributionsText.text = PolyApi.GenerateAttributions(includeStatic: false, runtimeAssets: assetsInUse);
        }
      }
      
      private void ImportDonuts(PolyAsset asset, PolyStatusOr<PolyImportResult> result) {
        assetCount++;
        result.Value.gameObject.transform.position = new Vector3(assetCount * 2.5f, 0f, 0f);
      }
}

Now we should see the attribution info display on our screen! It should include the title of the asset, the author, the url containing the model’s ID, and the license info.

Now we know that along with a large library of free 3D models to choose from, Poly also allows us to load them into our AR app on the fly and also handles attributions. That should be enough to kickstart your next AR project with Poly.

Poly is a great tool for game developers to quickly build AR apps populated with many different 3D models. It is also great for game developers who are trying to save on time and resources by adding assets into their game dynamically with Poly’s simple REST API. To check out some of the beautiful 3D models in the Poly library and to dig in to an in-depth guide and reference to the API, head to their website.

Poly assets used in this tutorial:

“Burger” by Poly by Google (Licence: Creative Commons CC-BY)

“Donut” by LaZZe (Licence: Creative Commons CC-BY)

“Donut 1” by Rachel Needle (Licence: Creative Commons CC-BY)

“Large Pink Frosted Doughnut” by Ruby “RubyDaSPLARK” (Licence: Creative Commons CC-BY)

“Pink frosted sprinkeled donut” by chocapic 360 (Licence: Creative Commons CC-BY)

HTML5 Game Development Mini-Degree

$
0
0

Learn to code and create a huge portfolio of both mobile and desktop games with the complete Phaser 3 and JavaScript game development curriculum.

The HTML5 Game Development Mini-Degree is the world’s most comprehensive on-demand curriculum. Learn to code and create impressive games with Phaser 3, the JavaScript library that took the developer community by storm, through a series of comprehensive, step-by-step video tutorials.

Beginning with a JavaScript 101 module for those who have never coded before, as you create a portfolio of HTML5 games you’ll progress through to more advanced topics, such as using ES6, Node.js, advanced JavaScript patterns, PhoneGap, Webpack and NPM,

Access this Mini-Degree on Zenva Academy

How to Create a Hyper Casual Game for Android – Part 1

$
0
0

During this three part tutorial series we will be creating a complete Hyper Casual Android game. The term “hyper casual” is a relatively new term used for simple but addictive games that have minimal art and bright colors. If you have been paying attention to the app stores, you will see many hyper casual games topping the charts.

The first part of the tutorial will cover the project, the player, and camera setup. We will be creating the sprites for this game right inside the Unity Editor, and will be adjusting the project settings so that they match what is needed for an Android platform mobile game playable in portrait 480 x 800 resolution.

To ensure that you will be able to effectively follow along with this tutorial you should be comfortable with:

  • C# programming language
  • Unity editor
  • Vector2 and Vector3
  • Creating folders, creating prefabs, adding and attaching scripts to game objects, adding components such as rigidbodies, scene creation, renaming scenes and game objects, creating tags, and adding colliders to gameobjects.
  • Understanding of the differences between Update, FixedUpdate, and LateUpdate functions.
  • Unity Remote 5 (For testing our Android game on a mobile device)

Source code files

You can download the tutorial source code files here. All the project files are located in the main folder. The asset folder contains additional intermediate files that were used during the process of creating the game sprites.

Before proceeding any further with this tutorial go ahead and make sure you are using Unity version 2018.2.13f1. You may use an older version of Unity, but you may notice differences between the version used in this tutorial and your version, and it may make it difficult to follow along effectively. Go ahead and create a new 2d project, you can name the project whatever you like. Switch the platform build settings to Android, generally Unity will default to windows build settings on a new project.

Folder Setup and Scene Creation

Create 3 new folders under assets in the project tab, name them: Sprites, Scripts, and Scenes (Alternatively, you may name them what you wish, but it’s common practice for good organization to keep naming conventions simple and obvious so that you know right where to navigate when looking for something in your projects). If you are using Unity version 2018.2.13f1, you may have noticed that there is already a folder in the assets section of the project tab named scenes. Unity now creates this folder and  a scene called “Sample Scene” with any new project that is created. Go ahead and rename the scene name to “Game.” Save the scene and project.

Camera Setup

In the Hierarchy tab select Main Camera and in the inspector tab change the following: Background color to a black or grey color, set the projection to Orthographic (Top down view), and the size to 10.

We will be writing some code later on in this tutorial series where we will change the background color so that it changes during game play. So, the initial color of the background really isn’t that important right now.

Player Sprite Creation and Setup

Navigate to the Sprites folder we have already created and right click, select create>Sprites>Circle. This circle will represent our player. Go ahead and drag the circle player into Hierarchy tab, and rename it to “Player.” There are a few settings in the inspector we need to adjust to finish up the initial player setup in the game scene. In the Inspector window on the transform component adjust the Y position to -8 and change the scale in the X and Y to 0.5. You can leave the Sprite color at white for now. Add a new script to the player object in the inspector and name it “Player.” This script will control all the behavior of our player.

The Player Script

Open the Player script up in your code editor, this tutorial uses Visual Studio. We need to add some movement behavior to our player. Go ahead and create a function called “PlayerMovement.” Create a FixedUpdate function under the Update function that is already in the script. We will be calling the “PlayerMovement” function in FixedUpdate. We also need to create a float variable called angle and set its initial value to 0 and create an int variable named xSpeed and set the initial value to 3.. We will add the following code to control the player movement on the X axis and also control the angle of the player as it moves. Essentially we will end up with the player moving back and forth on the X axis(Horizontally), you can go ahead and save the script and go back into the Unity Editor and hit play and make sure everything is working as intended. Later on we will be controlling the player movement by touch, so that the player will move upwards at an angle depending on where the player touches the game screen on the mobile device. Save the scene and project.

float angle = 0;
 int xSpeed = 3;

 private void FixedUpdate()
    {
     
        PlayerMovement();
        
    }

    void PlayerMovement()
    {

        Vector2 pos = transform.position;
        pos.x = Mathf.Cos(angle) * 5;
        
        transform.position = pos;
        angle += Time.deltaTime * xSpeed;
    }

Now we will create the “PlayerInput” function. Create this function right under the Player Movement function. This function will get the players touch input and then move our player(the circle) on screen upwards as long as the player touches the screen. So as long as the player holds their finger on the game screen on the mobile device the player object will move upwards. We also need to create a variable called ySpeed and set its initial value to 30. We need to add force to our rigidboody2D in order to get the player to move upwards as the screen is touched. We will need to create a variable for our Rigidbody2D component and name it PlayerRigi. Since our player object will soon have a rigidbody2d attached to it, and we will be moving the player object via the rigibody we need to create the variable and then get reference for it in the “Awake” function. You can create the “Awake” function right below are variable declarations.

Rigidbody2D PlayerRigi;

    float angle = 0;

    int xSpeed = 3;

    int ySpeed = 30;
    
    void Awake()
    {
        PlayerRigi = GetComponent<Rigidbody2D>();
    }

   
    private void FixedUpdate()
    {
        PlayerInput();
        PlayerMovement();
        
    }

    void PlayerMovement()
    {

        Vector2 pos = transform.position;
        pos.x = Mathf.Cos(angle) * 5;
        
        transform.position = pos;
        angle += Time.deltaTime * xSpeed;
    }

    void PlayerInput()
    {
        if (Input.GetMouseButton(0))
        {
            
            PlayerRigi.AddForce(new Vector2(0, ySpeed));
        }
        else
        {
            if(PlayerRigi.velocity.y > 0)
            {
                PlayerRigi.AddForce(new Vector2(0, -ySpeed));
            }
            else
            {
                PlayerRigi.velocity = new Vector2(PlayerRigi.velocity.x, 0);
            }
            
        }
    }

Save this script. Go back into the Unity Editor and test out our new code to make sure the player does indeed move upwards as the left mouse button is held down. Please note that if you test on your PC you will be holding the left mouse button down, but if you hook your mobile device to the Unity Editor via the Unity Remote 5 to test  that way you will hold your finger on the screen to get the player to move upwards, and the player object should continue to move upwards as you hold your finger down or press the left mouse button down.

Create and Attach a Trail Renderer to Our Player

Now that we have are player game object moving and responding to the player’s input we are going to create and attach a trail renderer component to the player. This trail renderer will kind of give the player game object a wave looking sort of effect behind it as it moves along in the game scene.

Click on the player object in the Hierarchy tab and click Add Component and in the search bar type trail renderer. Choose the trail renderer component. You will adjust the following settings on the trail renderer component in the inspector window: Materials, click the arrow beside it, and change the element 0 section to sprites-Default. Adjust the time to 0.5.

Now click on the width input box and change the value to 0.5. And using the mouse drag the red diamonds downwards to make it look like the screenshot below.

Now we are going to adjust the color and alpha settings of the trail renderer, so click on the color component on the trail renderer component in the inspector window, and another window will pop up called the Gradient Editor.

You will now click on the tab in the upper right hand corner of the gradient editor. Change the Location percentage to 58.2% and the Alpha to 0. Click on the grey tab in the upper left hand corner of the Gradient Editor and adjust the Alpha Setting to 100.

Now go ahead and hit the play button in the Unity Editor and test out our new spiffy trail renderer.

Setting Up the Camera to Follow Our Player

We now need to get our camera to constantly follow the player as they are moving. This Hyper Casual Game wouldn’t be complete if the player couldn’t see where they were going to avoid the obstacles we will be creating shortly. In order to not make this game so difficult, we need to include a Y offset on the camera so that the player can see a little bit ahead of themselves as they play to sort of “ready” themselves and be able to move at the right moment to avoid the obstacles ahead.

Click on the Main Camera in the Hierarchy tab, and add a new script component and name it “CamFollow.” Open the script up, and we will begin to write the code we need to get this camera following our player correctly.

We need to declare two variables: The first is the Transform of the player, you can call it playerTransform and the second is for the offset, make this a public float (So we can access it in the inspector) and call it “offset.”

private Transform playerTransform;
 public float offset;

We now need to create a tag for our Player and tag the Player because we are going to get reference to the Transform of the player in the start function. Remember, tags are case sensitive in Unity so make sure you type it correctly.

Once you have the Player Game Object tagged as Player, go ahead and get reference to the player transform in the start function; we are going to use the FindGameObjectWithTag(“Player”) to do so in the Start function(Be mindful here that you do type FindGameObjectWithTag and not FindGameObjectsWithTag, we have only one Player in our scene).

private void Start()
    {
        playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
    }

Now we need to create a “LateUpdate” function below the Start function. Why are we using LateUpdate, you may ask? Since we are moving the Player in FixedUpdate (FixedUpdate is called every fixed frame rate) on the Player script we are using LateUpdate so that we don’t get “jerky” camera movement, and since LateUpdate is called after FixedUpdate and that’s where the player movement is being handled, the player’s movement is already calculated so that we have more precise data on where to move our camera to follow the player more accurately.

Inside the LateUpdate function the current camera’s position needs to be stored in a temporary variable, call it Vector3 temp and set it to equal transform.position. This script is attached to our camera so we need to get the camera’s transform.position. Then we need to set the camera’s Y position to equal the player’s Y position. Then add the offset value to the temporary camera Y position. Then set the camera’s temporary position to the camera’s current position.

private void LateUpdate()
    {
        Vector3 temp = transform.position;

        temp.y = playerTransform.position.y;

        temp.y += offset;

        transform.position = temp;
    }

Save the script, and go test out the functionality in the Unity Editor by hitting the play button. You should see that our camera in the scene is now smoothly following our player as the game object moves upwards. If you click on the camera object in the Hierarchy you can watch the value in the transform component go up as the camera follows the player object.

As of right now we have our player moving, the camera following our player smoothly, player input is being taken in, and the player game object has a spiffy little trail renderer following behind it as it moves. This is a three part tutorial series, in part two we will tackle the first obstacles the player will need to avoid, we will handle collision of the obstacles and the player, keeping score and displaying it, and we will work on the game manager.

 


Source Code – Quintus Educational Game Course

Enhance Your Project with Objective Indicators – Part 1

$
0
0

Objective indicators are widely used in modern games as they offer the player a clear indication of their next target. In this tutorial we’ll create a basic method of guiding our player through a looping set of target positions. In the second part of this tutorial we’ll build upon this starter project to create an advanced game-ready asset.

To complete this project you will need a basic understanding of the Unity’s interface and workflow.

Asset Package

Before you begin, please download the related asset package here.

When the download is complete, unzip the package and make a note of the location.

Asset Usage

The original project assets are supplied under the Creative Commons 0 license.

Getting Started

Launch Unity and create a 3D project named ‘Indicator_System’

Once the project has loaded we’ll begin to set up the scene. If you’re using a legacy version of Unity, you need to create a ‘Scenes’ folder containing a new scene named ‘IndicatorSystem’. For recent versions you can simply rename the default scene.

Preparing The Scene

Before we get started we’ll adjust a few settings to suit the current project…

  • Open the Lighting Settings and set the Skybox to ‘None’
  • Set Environment Lighting Source to ‘Color’
  • Deselect the Lightmap Settings ‘Auto Generate’ checkbox

  • Save Your Scene

Importing The Asset Package

The supplied asset package contains a selection of basic components designed to get you up and running quickly. To install the package, right click on the ‘Assets’ folder and select ‘Import Package / Custom Package’ then navigate to the file you unzipped earlier.

Asset Package Contents…

  • Ground plane object
  • Ground material
  • Ground texture
  • Player cuboid object
  • Player material
  • Target object
  • Target material
  • Target texture
  • Indicator UI image

Adding Game Objects To The Scene

An empty scene is hardly inspiring, so let’s add some game objects. As mentioned above, the asset package contains three prefabs which have been designed to drop directly into the scene. We’ll start by dragging the ‘Ground’ and ‘Player ‘prefabs to the hierarchy window.

Adding The First Target Prefab

Before we add the ‘Target (0)’ prefab, we need to create an empty game object named ‘Targets’. This will be the target parent folder.

  • Right click inside the Hierarchy window and select ‘Create Empty’
  • Rename the empty object to ‘Targets’
  • Set the ‘Targets’ object position to 0, 0, 0

Now we can drag the ‘Target (0)’ prefab into the new ‘Targets’ object

Creating Multiple Targets

We’ll be needing more than one target in the scene so we’ll create three copies using the duplicate function…

  • Right click on the ‘Target (0)’ game object and select ‘Duplicate’
  • Repeat until you have four, sequentially numbered target objects

Setting The Target Object Positions

If we left the target objects in their default locations the objective system would be virtually redundant, so we’ll need to move each one to a different position on the ground plane. In the example images the targets are positioned at equal distances around the player, but you can decide for yourself where they should be.

Main Camera Setup

Later in this tutorial we’ll be creating a camera script, but first we’ll need to position the main camera behind the player object and set it’s culling mask…

  • Select the Main Camera and set it’s position to 0, 3, -12
  • Set the ‘Clear Flags’ value to ‘Solid Color’
  • Open the ‘GameObject’ menu and select ‘Align View to Selected’

Main Camera Settings

Now we’ll set the main camera’s culling mask by removing the ‘UI’ layer from the render list…

  • Open the Main Camera ‘Culling Mask’ menu and deselect the ‘UI’ layer

The UI Camera

We’ll be using a second camera to display the UI canvas elements, so right click inside the Hierarchy window and select ‘Camera’. Rename the new camera to ‘UICamera’ and set it’s position to 0, 0, 0

We’ll need to set the UICamera culling mask to render just the UI layer…

  • Set the UICamera ‘Clear Flags’ option to ‘Depth Only’
  • Set the UICamera ‘Culling Mask’ option to ‘Nothing’
  • Reselect ‘Culling Mask’ menu and select ‘UI’

At this point you should save the scene.

Adding The Canvas Component

Now we need to add a Canvas component which will be used to display the indicator element. To do this, right click inside the hierarchy window and select ‘UI / Canvas’. Feel free to delete the accompanying ‘Event System’ component as it’s unnecessary for this project.

Next we need to assign our UICamera by setting the Canvas ‘Render Mode’ to ‘Screen Space – Camera’ and dragging the UICamera to the ‘Render Camera’ slot.

We now have a main camera which will display the default layer and a secondary camera which will display the UI elements.

Next, we’ll add the indicator image and text component…

UI Indicator Components

  • Right click on the Canvas game object and select ‘UI / Image’
  • Rename the image to ‘Indicator’
  • Right click on the ‘Indicator’ image and select ‘UI / Text’
  • Rename the text object to ‘Label’

Indicator Image Settings

  • Select the Indicator object
  • Click the ‘Source Image’ circle icon and select ‘IndicatorTX’
  • Set the ‘Color’ value to hexadecimal E5D44D
  • Set the ‘Rect Transform’ width and height values to 32

We also need to set the indicator anchor point using the ‘Anchor Presets’ options…

  • Left click the ‘Inspector / Indicator’ anchor icon and select ‘Top Center’

  • Close the anchor preset panel
  • Set the ‘Rect Transform’ position to 0, -22, 0

Indicator Text Settings

Now we’ll set up the text label which will be used to display the distance between the player object and the current target position…

  • Set the ‘Label’ object anchor point to ‘Top Center’
  • Set the position to 0, -42, 0
  • Set the width to 100 and the height to 20

And we’ll adjust the text properties…

  • Change the ‘Text’ text to ‘0m’
  • Set the paragraph alignment to ‘Center / Top’
  • Change the text color to hexadecimal CCCCCC

At this point you should save the scene.

Script Folder Structure

We’ll store the project scripts in easily identifiable subfolders. This is a good habit to adopt as it improves readability, especially when creating larger projects.

Let’s start by creating a parent script folder…

  • Right click on the Assets folder and select ‘Create / Folder’
  • Rename the folder to ‘Scripts’

Now we’ll add the subfolders…

  • Create three subfolders and rename them ‘Camera’, ‘Manager’ and ‘Player’ respectively

The Player Script

The player script allows the user to control the player with a standard keyboard, joystick or gamepad. In practice, axis input is multiplied by a preset speed factor and uses frame sync to smooth the movement.

  • Right click on the ‘Player’ subfolder and select ‘Create / C# Script’
  • Name the script ‘PlayerControl’
  • Double click the script to open your code editor

Enter the following code into your ‘PlayerControl’ script…

using UnityEngine;

public class PlayerControl : MonoBehaviour {
    // Private Declarations
    private float verticalInput;
    private float horizonatlInput;
    private float playerSpeed;
    private float rotationSpeed;

	// --
	void Start () {
        // Set Default Values
        playerSpeed = 10.0f;
        rotationSpeed = 1.0f;
	}
	// --
	void Update () {
        // Get User Input From Keyboard / Game Controller
        verticalInput = Input.GetAxis("Vertical");
        horizonatlInput = Input.GetAxis("Horizontal");
        // Update Player Position
        transform.Translate(0, 0, verticalInput * playerSpeed * Time.deltaTime);
        // Update Player Rotation
        transform.Rotate(0, horizonatlInput * rotationSpeed, 0);
	}
}

  • Save the script and wait for Unity to compile the code
  • Drag the script onto the Player object
  • Save the scene

The Main Camera Script

The main camera script aligns the main camera using preset distance and height values. The smooth nature of the camera movement is achieved by framed synced interpolation.

  • Create a new C# script in the ‘Scripts / Camera’ folder
  • Rename the script to ‘FollowCam’
  • Double click the script to open your code editor

Enter the following code into your ‘FollowCam’ script…

using UnityEngine;

public class FollowCam : MonoBehaviour {
    // Public Declarations
    public Transform targetObject;
    public float cameraDistance = 12.0f;
    public float cameraHeight = 3.0f;
    public int heightDamping = 3;
    public int rotationDamping = 3;
    // Private Declarations
    private float currentHeight;
    private float currentAngle;
    private Quaternion currentRotation;

    // --
    void LateUpdate() {
        if (!targetObject) return;
        // Increment Camera Height & Angle
        currentHeight = Mathf.Lerp(transform.position.y, targetObject.position.y + cameraHeight, heightDamping * Time.deltaTime);
        currentAngle = Mathf.LerpAngle(transform.eulerAngles.y, targetObject.eulerAngles.y, rotationDamping * Time.deltaTime);
        // Populate Rotation Quaternion
        currentRotation = Quaternion.Euler(0, currentAngle, 0);
        // Set Camera Position
        transform.position = targetObject.position;
        transform.position -= currentRotation * Vector3.forward * cameraDistance;
        transform.position = new Vector3(transform.position.x, currentHeight, transform.position.z);
        // Look At Target
        transform.LookAt(targetObject);
    }
}

  • Save the script and wait for Unity to compile the code
  • Drag the script onto the Main Camera object
  • Drag the Player object to the FollowCam ‘Target Object’ slot

  • Save the scene

The Target Manager Script

The target manager script controls the UI based indicator using vector to viewport point conversion. Linear distance is calculated by a custom integer function whilst the relative position calculation is returned as a boolean which is used to control the indicator active status.

  • Create a new C# script in the ‘Scripts / Manager’ folder
  • Rename the script to ‘TargetManager’
  • Double click the script to open your code editor

Enter the following code into your ‘TargetManager’ script…

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class TargetManager : MonoBehaviour {
    // Public Declarations
    public Transform playerReference;
    public Image indicatorReference;
    public List<Transform> targetList;
    // Private Declarations
    private Vector3 indicatorVector;
    private Text textReference;
    private int targetRange;
    private int targetIndex;
    private bool targetValidated;

	// --
	void Awake () {
        // Store Indicator Text Object Reference
        textReference = indicatorReference.GetComponentInChildren<Text>();
	}
	// --
	void Start () {
        // Set Default Values
        targetIndex = 0;
        targetRange = 5;
        // Validate Target List Count
        targetValidated = targetList.Count > 0;
	}
    // --
    void LateUpdate() {
        // Check Player Status
        if (targetValidated) {
            if (LinearDistance(playerReference.position, targetList[targetIndex].position) < targetRange) {
                targetIndex = (targetIndex + 1) % targetList.Count;
            }
            // Update Indicator Position
            UpdateTargetSystem(targetIndex);
        }
    }
	// --
	public void UpdateTargetSystem (int index) {
        if (targetValidated) {
            // Set Indicator Status
            indicatorReference.gameObject.SetActive(RelativePosition(playerReference, targetList[index]));
            // Check Indicator Active Flag
            if (targetList[index].gameObject.activeInHierarchy) {
                // Update Distance Text
                textReference.text = LinearDistance(playerReference.position, targetList[index].position) + "m";
                // Update Indicator Rect Transform Position
                indicatorVector = indicatorReference.rectTransform.anchorMin;
                indicatorVector.x = Camera.main.WorldToViewportPoint(targetList[index].position).x;
                indicatorReference.rectTransform.anchorMin = indicatorVector;
                indicatorReference.rectTransform.anchorMax = indicatorVector;
            }
        }
	}
    // --
    public int LinearDistance(Vector3 playerPosition, Vector3 targetPosition) {
        // Zero YAxis
        playerPosition.y = 0;
        targetPosition.y = 0;
        // Return Linear Distance
        return Mathf.RoundToInt(Vector3.Distance(playerPosition, targetPosition));
    }
    // --
    private bool RelativePosition(Transform player, Transform target) {
        // Calculate Relavtive Position
        return Vector3.Dot(Vector3.forward, player.InverseTransformPoint(target.position).normalized) > 0;
    }
}

  • Save the script and wait for Unity to compile the code
  • Drag the script onto the ‘Targets’ game object

Now we’ll add component references to the Target Manager script…

  • Drag the ‘Player’ object to the ‘Player Reference’ slot
  • Drag the UI ‘Indicator’ object to the ‘Indicator Reference’ slot
  • Left click to open the ‘Target List’ field and set the size to 4
  • Drag each target to the corresponding ‘Target List’ slot

  • Save the scene

Testing The Project

To check that everything’s working as expected, click the ‘Play’ button and use the arrow keys to move the player towards the target indicator. When you’re within range of the current target the indicator will switch to the next objective. The target indicator will loop through the available target objects.

Upgrade This Project In Part 2

In the next part of this tutorial we’ll be upgrading the project with code optimisation, multiple indicators, user defined waypoints and custom dynamic link libraries. The finished project will allow you to create a fully portable system for use in your own creations

How to create a Turn-Based RPG in Phaser 3 – Part 3

$
0
0

In Part One of this tutorial we created the WorldScene, and in Part Two we made the BattleScene. If you haven’t read them I strongly recommend for you to do so before continuing. Now, in Part Three, we will combine both scenes into a working game.

Source code

You can download the files for this tutorial here.

Assets

All assets used in this tutorial are CC0 licensed. The tiles are created by Kenney Vleugels and you can download them at www.kenney.nl
Player characters – https://opengameart.org/content/rpg-character-sprites
Enemies – https://opengameart.org/content/dragon-1

Scene Switching

At the start, I will try to simplify and explain the scene switching logic. We will use the final version of the WorldScene from the first part of the tutorial and we will create very basic BattleScene and UIScene.

Get your WorldScene working and then edit the config object to add BattleScene and UIScene. Edit it to look like this:

var config = {
    type: Phaser.AUTO,
    parent: 'content',
    width: 320,
    height: 240,
    zoom: 2,
    pixelArt: true,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 0 },
            debug: false // set to true to view zones
        }
    },
    scene: [
        BootScene,
        WorldScene,
        BattleScene,
        UIScene
    ]
};
var game = new Phaser.Game(config);

And here are our simplified BattleScene and UIScenes. We will use them to show how the scene switching will work:

var BattleScene = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function BattleScene ()
    {
        Phaser.Scene.call(this, { key: 'BattleScene' });
    },
    create: function ()
    {
        // set the background of the main scene green
        this.cameras.main.setBackgroundColor('rgba(0, 200, 0, 0.5)');
        // Run UI Scene at the same time
        this.scene.run('UIScene');
    }
});

var UIScene = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function UIScene ()
    {
        Phaser.Scene.call(this, { key: 'UIScene' });
    },

    create: function ()
    {       
        this.graphics = this.add.graphics();
        this.graphics.lineStyle(1, 0xffffff);
        this.graphics.fillStyle(0x031f4c, 1);        
        this.graphics.strokeRect(2, 150, 90, 100);
        this.graphics.fillRect(2, 150, 90, 100);
        this.graphics.strokeRect(95, 150, 90, 100);
        this.graphics.fillRect(95, 150, 90, 100);
        this.graphics.strokeRect(188, 150, 130, 100);
        this.graphics.fillRect(188, 150, 130, 100);
    }
});

Now we need to change the WorldScene to start the BattleScene. Change WorldScene onMeetEnemy like this:

onMeetEnemy: function(player, zone) {        
        // we move the zone to some other location
        zone.x = Phaser.Math.RND.between(0, this.physics.world.bounds.width);
        zone.y = Phaser.Math.RND.between(0, this.physics.world.bounds.height);
        
        // shake the world
        this.cameras.main.shake(300);
        
        // switch to BattleScene
        this.scene.switch('BattleScene');
    },

If you run the game now, you will see that, when the player hits an invisible enemy, it starts BattleScene. And BattleScene will control the UIScene. Lets see how to return back to the WorldScene. I will create a BattleScene method exitBattle:

exitBattle: function() {
        this.scene.sleep('UIScene');
        this.scene.switch('WorldScene');
    },

This function will sleep the UIScene (make it not active and not visible) and will switch from BattleScene to WorldScene. For now I will add simple time event that will call this function after 2 seconds.
Add this to the end of BattleScene create method:

var timeEvent = this.time.addEvent({delay: 2000, callback: this.exitBattle, callbackScope: this});

Now we have a problem. Everything works only the first time we switch to BattleScene. The second time, its create function is not called so we don’t have UIScene visible and we don’t exit BattleScene after the given time.
To fix this we need to listen to Scene ‘wake’ event. Add this code at the end of BattleScene create function:

this.sys.events.on('wake', this.wake, this);

We need to add the wake function. It will run the UIScene and will add a timed event to exit the BattleScene:

wake: function() {
        this.scene.run('UIScene');  
        this.time.addEvent({delay: 2000, callback: this.exitBattle, callbackScope: this});        
    },

Now our simple BattleScene should work just fine each time we enter a battle.

Make the game work

Its time to change the BattleScene code to the one from the second part of this tutorial. Remove only its BootScene as we don’t need two boot scenes to load our game. You may need to add the resources of the enemies to the current BootScene loader.

// enemies
        this.load.image("dragonblue", "assets/dragonblue.png");
        this.load.image("dragonorrange", "assets/dragonorrange.png");

Its time to change the unit class like this:

var Unit = new Phaser.Class({
    Extends: Phaser.GameObjects.Sprite,

    initialize:

    function Unit(scene, x, y, texture, frame, type, hp, damage) {
        Phaser.GameObjects.Sprite.call(this, scene, x, y, texture, frame)
        this.type = type;
        this.maxHp = this.hp = hp;
        this.damage = damage; // default damage     
        this.living = true;         
        this.menuItem = null;
    },
    // we will use this to notify the menu item when the unit is dead
    setMenuItem: function(item) {
        this.menuItem = item;
    },
    // attack the target unit
    attack: function(target) {
        if(target.living) {
            target.takeDamage(this.damage);
            this.scene.events.emit("Message", this.type + " attacks " + target.type + " for " + this.damage + " damage");
        }
    },    
    takeDamage: function(damage) {
        this.hp -= damage;
        if(this.hp <= 0) {
            this.hp = 0;
            this.menuItem.unitKilled();
            this.living = false;
            this.visible = false;   
            this.menuItem = null;
        }
    }    
});

Here you will see the variable menuItem. We will link each unit to its menu item, and when the unit is dead, it will notify the menu item for this, so the player won’t be able to select a killed enemy.
Also we have new member variable – living. We will use it to check if the current unit is alive. Only living units will be able to participate in battle.

Now we need to edit the next unit attack circle to take in consideration this new property. Change BattleScene nextTurn method to this:

nextTurn: function() {        
        do {
            this.index++;
            // if there are no more units, we start again from the first one
            if(this.index >= this.units.length) {
                this.index = 0;
            }
        } while(this.units[this.index].living);
        
        // if its player hero
        if(this.units[this.index] instanceof PlayerCharacter) {                
            this.events.emit("PlayerSelect", this.index);
        } else { // else if its enemy unit
            // pick random hero
            var r = Math.floor(Math.random() * this.heroes.length);
            // call the enemy"s attack function 
            this.units[this.index].attack(this.heroes[r]);  
            // add timer for the next turn, so will have smooth gameplay
            this.time.addEvent({ delay: 3000, callback: this.nextTurn, callbackScope: this });
        }
    },

Here we increment the index until we get a living unit. The units that are dead won’t be able to move any more. But now we have a problem. We will have endless cycle if there are no living units. To avoid this, we must check for game over or victory.

Add this code at the top of nextTurn:

if(this.checkEndBattle()) {           
            this.endBattle();
            return;
        }

And here is how checkEndBattle should look:

checkEndBattle: function() {        
        var victory = true;
        // if all enemies are dead we have victory
        for(var i = 0; i < this.enemies.length; i++) {
            if(this.enemies[i].living)
                victory = false;
        }
        var gameOver = true;
        // if all heroes are dead we have game over
        for(var i = 0; i < this.heroes.length; i++) {
            if(this.heroes[i].living)
                gameOver = false;
        }
        return victory || gameOver;
    },

And here is endBattle:

endBattle: function() {       
        // clear state, remove sprites
        this.heroes.length = 0;
        this.enemies.length = 0;
        for(var i = 0; i < this.units.length; i++) {
            // link item
            this.units[i].destroy();            
        }
        this.units.length = 0;
        // sleep the UI
        this.scene.sleep('UIScene');
        // return to WorldScene and sleep current BattleScene
        this.scene.switch('WorldScene');
    },

The final version of nextTurn:

nextTurn: function() {  
        // if we have victory or game over
        if(this.checkEndBattle()) {           
            this.endBattle();
            return;
        }
        do {
            // currently active unit
            this.index++;
            // if there are no more units, we start again from the first one
            if(this.index >= this.units.length) {
                this.index = 0;
            }            
        } while(!this.units[this.index].living);
        // if its player hero
        if(this.units[this.index] instanceof PlayerCharacter) {
            // we need the player to select action and then enemy
            this.events.emit("PlayerSelect", this.index);
        } else { // else if its enemy unit
            // pick random living hero to be attacked
            var r;
            do {
                r = Math.floor(Math.random() * this.heroes.length);
            } while(!this.heroes[r].living) 
            // call the enemy's attack function 
            this.units[this.index].attack(this.heroes[r]);  
            // add timer for the next turn, so will have smooth gameplay
            this.time.addEvent({ delay: 3000, callback: this.nextTurn, callbackScope: this });
        }
    },

As we have seen from the simple version of BattleScene we need to listen for the wake event. I’ve created new function startBattle and moved more of the starting logic there. Here is how my startBattle looks:

startBattle: function() {
        // player character - warrior
        var warrior = new PlayerCharacter(this, 250, 50, "player", 1, "Warrior", 100, 20);        
        this.add.existing(warrior);
        
        // player character - mage
        var mage = new PlayerCharacter(this, 250, 100, "player", 4, "Mage", 80, 8);
        this.add.existing(mage);            
        
        var dragonblue = new Enemy(this, 50, 50, "dragonblue", null, "Dragon", 50, 3);
        this.add.existing(dragonblue);
        
        var dragonOrange = new Enemy(this, 50, 100, "dragonorrange", null,"Dragon2", 50, 3);
        this.add.existing(dragonOrange);
        
        // array with heroes
        this.heroes = [ warrior, mage ];
        // array with enemies
        this.enemies = [ dragonblue, dragonOrange ];
        // array with both parties, who will attack
        this.units = this.heroes.concat(this.enemies);
        
        this.index = -1; // currently active unit
        
        this.scene.run("UIScene");        
    },

And here is how the BattleScene create function changes:

create: function () {    
        // change the background to green
        this.cameras.main.setBackgroundColor("rgba(0, 200, 0, 0.5)");
        this.startBattle();
        // on wake event we call startBattle too
        this.sys.events.on('wake', this.startBattle, this);             
    },

Now we will change the MenuItem class like this:

var MenuItem = new Phaser.Class({
    Extends: Phaser.GameObjects.Text,
    
    initialize:
            
    function MenuItem(x, y, text, scene) {
        Phaser.GameObjects.Text.call(this, scene, x, y, text, { color: "#ffffff", align: "left", fontSize: 15});
    },
    
    select: function() {
        this.setColor("#f8ff38");
    },
    
    deselect: function() {
        this.setColor("#ffffff");
    },
    // when the associated enemy or player unit is killed
    unitKilled: function() {
        this.active = false;
        this.visible = false;
    }
    
});

Here you can see the unitKilled method called by the units on dying. This will deactivate and hide the menu item. Now we must change the menu navigation, so when the selection moves up or down, it will skip the deactivated items.
Here is how to change Menu methods moveSelectionUp and moveSelectionDown:

moveSelectionUp: function() {
        this.menuItems[this.menuItemIndex].deselect();
        do {
            this.menuItemIndex--;
            if(this.menuItemIndex < 0)
                this.menuItemIndex = this.menuItems.length - 1;
        } while(!this.menuItems[this.menuItemIndex].active);
        this.menuItems[this.menuItemIndex].select();
    },
    moveSelectionDown: function() {
        this.menuItems[this.menuItemIndex].deselect();
        do {
            this.menuItemIndex++;
            if(this.menuItemIndex >= this.menuItems.length)
                this.menuItemIndex = 0;
        } while(!this.menuItems[this.menuItemIndex].active);
        this.menuItems[this.menuItemIndex].select();
    },

We need to change the select method too, so when the menu is activated, it will select an active item:

select: function(index) {
        if(!index)
            index = 0;       
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex = index;
        while(!this.menuItems[this.menuItemIndex].active) {
            this.menuItemIndex++;
            if(this.menuItemIndex >= this.menuItems.length)
                this.menuItemIndex = 0;
            if(this.menuItemIndex == index)
                return;
        }        
        this.menuItems[this.menuItemIndex].select();
        this.selected = true;
    },

This is the moment to mention, that I decided to change the name of the event SelectEnemies to SelectedAction. Also, I changed the listeners to mach the name. You can continue with the old name or use the new one. The best way is to just use replace all and your ide will do everything automatically. As the event is fired when the player have selected an action, I think the code now is a bit more self explanatory.

We need to change the Menu remap method too:

remap: function(units) {
        this.clear();        
        for(var i = 0; i < units.length; i++) {
            var unit = units[i];
            unit.setMenuItem(this.addMenuItem(unit.type));            
        }
        this.menuItemIndex = 0;
    },

And change Menu addMenuItem to this:

addMenuItem: function(unit) {
        var menuItem = new MenuItem(0, this.menuItems.length * 20, unit, this.scene);
        this.menuItems.push(menuItem);
        this.add(menuItem); 
        return menuItem;
    },

Now we are almost done with the changes. What is left, is to fix the UIScene and the WorldScene to listen for wake event. I will start with the UIScene. The code that is responsible for the global menu creation will stay at its create method, but the code responsible for the specific battle will go to new function. Change UIScene create method to this:

create: function ()
    {    
        // draw some background for the menu
        this.graphics = this.add.graphics();
        this.graphics.lineStyle(1, 0xffffff);
        this.graphics.fillStyle(0x031f4c, 1);        
        this.graphics.strokeRect(2, 150, 90, 100);
        this.graphics.fillRect(2, 150, 90, 100);
        this.graphics.strokeRect(95, 150, 90, 100);
        this.graphics.fillRect(95, 150, 90, 100);
        this.graphics.strokeRect(188, 150, 130, 100);
        this.graphics.fillRect(188, 150, 130, 100);
        
        // basic container to hold all menus
        this.menus = this.add.container();
                
        this.heroesMenu = new HeroesMenu(195, 153, this);           
        this.actionsMenu = new ActionsMenu(100, 153, this);            
        this.enemiesMenu = new EnemiesMenu(8, 153, this);   
        
        // the currently selected menu 
        this.currentMenu = this.actionsMenu;
        
        // add menus to the container
        this.menus.add(this.heroesMenu);
        this.menus.add(this.actionsMenu);
        this.menus.add(this.enemiesMenu);
                
        this.battleScene = this.scene.get("BattleScene");                                
        
        // listen for keyboard events
        this.input.keyboard.on("keydown", this.onKeyInput, this);   
        
        // when its player cunit turn to move
        this.battleScene.events.on("PlayerSelect", this.onPlayerSelect, this);
        
        // when the action on the menu is selected
        // for now we have only one action so we dont send and action id
        this.events.on("SelectedAction", this.onSelectedAction, this);
        
        // an enemy is selected
        this.events.on("Enemy", this.onEnemy, this);
        
        // when the scene receives wake event
        this.sys.events.on('wake', this.createMenu, this);
        
        // the message describing the current action
        this.message = new Message(this, this.battleScene.events);
        this.add.existing(this.message);        
        
        this.createMenu();     
    },

You see now that we call createMenu both when we start the UIScene for the first time and when its started through wake event. Here is our createMenu:

createMenu: function() {
        // map hero menu items to heroes
        this.remapHeroes();
        // map enemies menu items to enemies
        this.remapEnemies();
        // first move
        this.battleScene.nextTurn(); 
    },

After running the game now you may found a problem. If its player turn, and he press multiple times space when selecting an enemy, nextTurn is called multiple times and you can witness strange behavior. To fix this we must be sure that we send only one event per menu. To do so, I’ve decide to add property selected to the Menu class. When Menu gets focus, this property becomes true. When the player chooses an action, it becomes false and the Menu won’t get its action method called again.
Here is how the Menu class should look at the end:

var Menu = new Phaser.Class({
    Extends: Phaser.GameObjects.Container,
    
    initialize:
            
    function Menu(x, y, scene, heroes) {
        Phaser.GameObjects.Container.call(this, scene, x, y);
        this.menuItems = [];
        this.menuItemIndex = 0;
        this.x = x;
        this.y = y;        
        this.selected = false;
    },     
    addMenuItem: function(unit) {
        var menuItem = new MenuItem(0, this.menuItems.length * 20, unit, this.scene);
        this.menuItems.push(menuItem);
        this.add(menuItem); 
        return menuItem;
    },  
    // menu navigation 
    moveSelectionUp: function() {
        this.menuItems[this.menuItemIndex].deselect();
        do {
            this.menuItemIndex--;
            if(this.menuItemIndex < 0)
                this.menuItemIndex = this.menuItems.length - 1;
        } while(!this.menuItems[this.menuItemIndex].active);
        this.menuItems[this.menuItemIndex].select();
    },
    moveSelectionDown: function() {
        this.menuItems[this.menuItemIndex].deselect();
        do {
            this.menuItemIndex++;
            if(this.menuItemIndex >= this.menuItems.length)
                this.menuItemIndex = 0;
        } while(!this.menuItems[this.menuItemIndex].active);
        this.menuItems[this.menuItemIndex].select();
    },
    // select the menu as a whole and highlight the choosen element
    select: function(index) {
        if(!index)
            index = 0;       
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex = index;
        while(!this.menuItems[this.menuItemIndex].active) {
            this.menuItemIndex++;
            if(this.menuItemIndex >= this.menuItems.length)
                this.menuItemIndex = 0;
            if(this.menuItemIndex == index)
                return;
        }        
        this.menuItems[this.menuItemIndex].select();
        this.selected = true;
    },
    // deselect this menu
    deselect: function() {        
        this.menuItems[this.menuItemIndex].deselect();
        this.menuItemIndex = 0;
        this.selected = false;
    },
    confirm: function() {
        // when the player confirms his slection, do the action
    },
    // clear menu and remove all menu items
    clear: function() {
        for(var i = 0; i < this.menuItems.length; i++) {
            this.menuItems[i].destroy();
        }
        this.menuItems.length = 0;
        this.menuItemIndex = 0;
    },
    // recreate the menu items
    remap: function(units) {
        this.clear();        
        for(var i = 0; i < units.length; i++) {
            var unit = units[i];
            unit.setMenuItem(this.addMenuItem(unit.type));            
        }
        this.menuItemIndex = 0;
    }
});

And here how we will change the UIScene onKeyInput:

onKeyInput: function(event) {
        if(this.currentMenu && this.currentMenu.selected) {
            if(event.code === "ArrowUp") {
                this.currentMenu.moveSelectionUp();
            } else if(event.code === "ArrowDown") {
                this.currentMenu.moveSelectionDown();
            } else if(event.code === "ArrowRight" || event.code === "Shift") {

            } else if(event.code === "Space" || event.code === "ArrowLeft") {
                this.currentMenu.confirm();
            } 
        }
    },

Now when you run the game almost everything should run fine. But sometimes when you go back from BattleScene to WorldScene, the character is not waiting patiently but moving in some direction and you need to press and release the key for this direction to stop it. Its small bug, but we have to fix it. We need to wait for WorldScene wake event and then reset the keys.

Add this row to the end of WorldScene create function:

this.sys.events.on('wake', this.wake, this);

And here is how the wake method should look:

wake: function() {
        this.cursors.left.reset();
        this.cursors.right.reset();
        this.cursors.up.reset();
        this.cursors.down.reset();
    },

With this we are finished with the third part of the tutorial. Although we have done a lot of work, our game need much more work to be fully functional. Get it as an exercise to save the state of the player characters and to make them get experience and levels. Also you can add more actions to the Actions menu and pass them together with the events.

Create Your First VR App with Unity – Unite India 2018

Hey! Listen: Sound Design with Unity & FMOD – Part 2

$
0
0

Introduction

Welcome to Part 2 of my sound design series about implementing sound events using Unity and FMOD, which looks at making our games more sight-impaired-accessible. Designing for accessibility is not just great for inclusivity, it also helps make a more atmospheric game for everyone! This tutorial follows directly on from the last, so if you haven’t read Part 1, please check it out first here.

In Part 1 we set up an FMOD Studio project and integrated it into Unity. We then covered the basics of FMOD events, the foundation for most of our game audio logic, taking a lot of programming effort from Unity and presenting it in a more intuitive fashion in FMOD Studio. In this tutorial we will be learning about Multi Instruments to add more variation and setting up some more ambiance via changing footstep sounds based on the surface that the player is walking on. I have included a custom footsteps script that demonstrates accessing FMOD logic and firing events. Let’s get to it!

Import Sound Files Into FMOD Studio

Start by opening your existing project in Unity and also the integrated FMOD project in FMOD Studio. We will be using a combination of assets from Part 1 and Part 2 for this tutorial. The Part 2 assets add a 3d model of a printer, a printer sound and extra computer sounds we will be using to add more variation. We will be using the persistent printer sound in the next tutorial where we will be diving into Google Resonance zones so hold on to that for now.

Let’s import all of the sound files we’ll be using into the FMOD Audio Bin. If, like me, you haven’t yet added the unused sound effects from Part 1 to your FMOD Audio Bin then let’s do that now. In FMOD Studio go to Window > Audio Bin (or press Ctrl + 3 on your keyboard). Now drag all the footstep sound files in Part 1 from your file explorer into the Audio Bin window.

Next do the same for the extra computer and printer sounds from the Part 2 assets zip folder. Remember that this process creates duplicates of the files in your FMOD Studio project folder so you probably shouldn’t be importing files that are already in your Unity or FMOD project folders or you’ll be wasting a lot of disk space (high-quality sound files are quite hefty). If you already put sound files in the project folders just move them to the FMOD Project/Assets folder – this is the target folder for the Audio Bin.

FMOD Multi-Instrument Containers

The first thing we’re going to do in FMOD Studio is add some extra sounds and random variation to the persistent computer_hum audio event. Instead of just humming, I want to add some random clicks and beeps that computers make using FMOD Multi Instruments (formerly known as Multi Sound in case of any confusion). This also serves as good practice because we’ll be using Multi instruments for the footsteps ahead. Let’s put the Multi Instrument on a second audio track in the computer_hum event. Right-click on the existing audio track header and click Add Audio Track.

Then right-click on the body of the new audio track and click Add Multi Instrument.

Now click on the Multi Instrument to select it. Notice that the dock at the bottom of the FMOD Studio window now contains a section called Playlist. There is also an icon of a dice highlighted in yellow by default above the playlist. This makes the Multi Instrument play one of the items in the playlist at random and that’s what we want, for variation’s sake, so let’s leave it like that. Now let’s open the Audio Bin and drag in the three computer sounds into the Multi Instrument playlist.

Adding Variation to the Multi Instrument Container

Now we’re going to add random variation using Modulation we learned about in Part 1. Expand the dock at the bottom of the FMOD Studio window by clicking the little white arrow above the text Trigger Behaviors. This lets us access the dials we need to modulate the behaviour of the Multi Instrument container when it is triggered. I’ve added Modulation to most of the settings here but it’s up to you if you want to use all of them. Hold Alt while you click and drag dials to add Modulation (Optn on Mac) shown by a thick red or green line.

I think Start Offset is the most important so that the audio doesn’t always play at the same time. Probability is the new one you might want to use here here, but I don’t think modulating it really does much considering it’s already based on chance. Either way, enable it by clicking the dice and lowering the dial below 100% for a chance that the Multi Instrument will NOT play when it is called. It’s ok though, because (if you’ve followed the previous tutorial) we have a loop region for this event, it means there might just be a bit of a gap in that specific audio track before the next loop. I’ve also brought the volume of the Multi Instrument down because I don’t want these random sounds to be overly-invasive, they’re not critical to the game after all. Remember, you should only modulate the volume and pitch a tiny bit otherwise it may sound like it’s moving (see the Doppler effect) and make it confusing for a sight-impaired player.

Play with the length of the Multi Instrument container by clicking and dragging on the right edge of the selection box (yellow outline). You don’t need to, but I suggest trying the loop setting on the playlist of the Multi Instrument itself. Turning that on and tweaking the length of the Multi Instrument container on it’s audio track allows you to play with the space between random noises. Lastly, you should rename the two audio tracks for this event to something more descriptive by right-clicking the header. Don’t forget to Save and then Build before trying out the new persistent computer sounds in Unity. It goes without saying that decent headphones will really improve the spatial audio experience.

Container – Footstep Changing Logic In FMOD

We’re going to be going through the above steps again using a Multi Instrument container on two different audio tracks for the footstep sounds. Right-click in the Events tab and add a New Event, call it footsteps or something similar. In this footsteps event, create and name separate audio tracks for each type of surface that your game has. My boring demo just has concrete and wood so I only have two audio tracks but I recommend you take the time right now to collect some more for your game – when it comes to ambiance in video games, more is more after all! I also think you should have at least three different footstep files for every surface your character can walk on in your game. Just two different footsteps played at random can become very monotonous.  

Now add a Multi Instrument container to each audio track and drag the appropriate list of sound files you’ve collected from the Audio Bin into the playlist for each. Don’t forget to resize each Multi Instrument container to be able to play all of the longest sound in each playlist! It should help to use the square bracket keys to zoom in and out of the audio track ([,]). As for Modulation, we want plenty of pitch variation but not so much volume. We certainly don’t want any random Start Offset or Probability but we do want the playlist to be random which it is by default anyway. Come back to tweak the specific Multi Instrument settings after we’ve set it up in Unity later.

Volume Automation

Next is the clever part. We will be using parameters to adjust the volume of each audio track independently. To allow this we first have to add Automation for the volume of each audio track. Do this by right-clicking the volume dial of one of the audio tracks and clicking Add Automation.  

The volume automation displays like another audio track in the event Timeline tab. Now we must associate volume levels with a particular value for a new parameter for each type of footstep. Make a new parameter by clicking the little “+” next to the Timeline tab header then clicking New Parameter. Name the parameters the same as the audio tracks that they will affect and keep the values from 0 to 1. Notice how new dials have appeared at the top of the footsteps event window and there is a new tab for each parameter next to the Timeline tab.

Next we link those parameter values to the volume of the different audio tracks. Let’s go to the first parameter tab we created (mine is concrete) and adjust the volume level to -oo dB when the parameter equals zero. Click and drag on the red dotted line under the audio track with the matching name to add a key and move it – down to the far bottom-left of the volume “track”. Then do the same for the far right, moving the volume to 0.00dB when the parameter equals one. It should look like the below image. Take note that I have zoomed out with the open square brackets ([) key so that I can see the whole of the parameter value range. Unlike the Timeline, this value at the top of the parameter tab is range, not time. So it should go from 0 to 1 (the range we set earlier) and that’s why you can’t zoom out any more than that.

Link the track volume to the matching parameter range in the same way for all the different footstep surface types – be sure to pay careful attention to which parameter tab you are in! When you’re done, play the event and fiddle with the parameter dials above the timeline. Notice how you can adjust the parameter dials to fuse the different surface sounds together? You can move these parameters into any value for high-fidelity, high-control ambiance. Perhaps wet dirt could be a combination of your dirt and the mud footstep sounds?

Seek Speed

There’s actually a simple setting that makes it easy for us to smoothly transition between different types of surface sounds using multiple audio tracks like this – it’s called Seek Speed. This is the rate at which a certain parameter will move towards a new target value such as one sent from Unity. This lets us just send a value of zero or one from Unity that adjusts smoothly with a value we can tweak in-editor per surface type! You can find Seek Speed by simply clicking on the name of the parameter next to it’s dial at the top of the event window.

Seek Speed is instant by default but if you want it to be gradual, you need to click and drag on the dial. The Asymmetric option just allows you to have a different rate for fading in and out, or, ascending and descending. Adding a loop region temporarily to this event will make it much easier to determine what rate you want. Playing with the parameter dials while the event is looping will allow you to audibly-and-visibly gauge the appropriate Seek Speed. Don’t forget, you will need to set Seek Speed per each parameter.

Stereo Footsteps Fix

We have used a 3D event for our footsteps in case we want this same event to be used for other characters in the 3D world. But we could have a bit of a problem if we’re using stereo sounds – it’s very odd to hear footsteps pan because, as humans, we hear our footsteps from directly below. You can fix this two ways; convert the file to mono in Audacity or a similar application, or just add a Channel Mix effect in FMOD Studio – Select the audio track you want to add an effect to, right-click the black area in the dock at the bottom of the window, click Add Effect > Channel Mix. Then change it’s grouping to mono.  

Multi Instrument & Volume Automation Final Thoughts

Perhaps we could add another Multi Instrument container to another audio track for wood creaks (volume-controlled by the wood parameter) that has a low Probability to play when the event is called? I imagine that would be awesome for horror games! When you’ve finished wondering about the many varieties of ambient sound combinations that Multi Instrument containers and volume automation provide, let’s set up accessing custom parameters in Unity.

Before you leave FMOD Studio though, don’t forget to assign the footsteps event to the master bank by right-clicking it in the Events tab. Then Save and Build your FMOD Studio project and open your Unity project.

FMOD Footsteps in Unity

To fire the footstep event we have made in FMOD Studio we will be using a custom C# script that I just wrote. This script needs to be attached to our Rigid Body FPS Controller game object because it needs to access components of both the RigidbodyFirstPersonController and the HeadBob scripts. We do not need need to add a StudioEventEmitter script like last time because we will be accessing our footsteps event in our script.

With the RigidBodyFPSController game object selected, click Add Component > New Script and name it FMODFootsteps.

I’ll paste the entire script below, take your time now to read through my comments to better understand what’s going on. You can use this script however you want but don’t run away just yet! – there’s a bit to explain here and I have plenty of future recommendations for your own tweaks ahead!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput; // Use to access input for all platforms
using UnityStandardAssets.Characters.FirstPerson; // Access FPS classes

[RequireComponent(typeof(CapsuleCollider))] // A collider is required to check surface types
// Attach this to your rigidbody FPS controller game object
public class FMODFootsteps : MonoBehaviour
{
    [FMODUnity.EventRef] // This allows us to use the search function to fill in the path string below
    public string eventPath;

    [Range(0, 1)]
    public float concrete, wood; // Only make public for debugging purposes

    public RigidbodyFirstPersonController myFPSController; // Access FPS controller for player state information
    public HeadBob myHeadBob; // Access head bob component for stride length
    public Vector3 footstepOffset; // An optional transform offset for the footstep audio event instances

    float stepCycle, nextStep = 0.1f; // Keep track of when we want to play the footstep event
    float inputX, inputY; // Just shorthand variables for player input checks
    Rigidbody myRigidbody; // Use to check if player is moving

    // Use Start to setup references
    void Start()
    {
        // If no FPS controller has been setup, look for one attached to this game object
        if (myFPSController == null)
            myFPSController = GetComponent<RigidbodyFirstPersonController>();

        myRigidbody = GetComponent<Rigidbody>();

        // Access attached head bob component in children if none has been supplied in editor
        if (myHeadBob == null)
            myHeadBob = GetComponentInChildren<HeadBob>();
    }

    // Update is called once per frame, use it to check for inputs
    void Update()
    {
        // Update axis inputs so we know the player wants to move
        inputX = CrossPlatformInputManager.GetAxis("Horizontal");
        inputY = CrossPlatformInputManager.GetAxis("Vertical");

        // If you hit jump and player is not already jumping then play a footstep sound
        if (CrossPlatformInputManager.GetButtonDown("Jump") && !myFPSController.Jumping)
        {
            PlayFootStepEvent();
        }

    }

    // Fixed Update is called once per physics step
    private void FixedUpdate()
    {
        // Set all parameters to 0 as soon as the player is not grounded as a failsafe
        if (!myFPSController.Grounded)
        {
            concrete = 0f;
            wood = 0f;
        }

        UpdateStepCycle();
    }

    // Check if ready for next step and play event if so
    void UpdateStepCycle()
    {
        // If the FPS controller is moving and the player wants to move update step cycle while considering if player is running
        if (myRigidbody.velocity.sqrMagnitude > 0 && (inputX != 0 || inputY != 0))
        {
            stepCycle += 
                (myRigidbody.velocity.magnitude * (myFPSController.Running ? myHeadBob.RunningStrideLengthen : 1f)) * Time.fixedDeltaTime;
        }

        // If step cycle has passed distance for next step update next step and play footstep
        if (stepCycle > nextStep)
        {
            nextStep = stepCycle + myHeadBob.StrideInterval / 2;

            PlayFootStepEvent();
        }
    }

    // Handles playing footstep event instance based on path
    private void PlayFootStepEvent()
    {
        // If FPS controller is grounded update parameters and play footstep event
        if (myFPSController.Grounded && eventPath != null)
        {
            FMOD.Studio.EventInstance footstepEvent = FMODUnity.RuntimeManager.CreateInstance(eventPath);

            // Set position of event with optional offset
            footstepEvent.set3DAttributes(FMODUnity.RuntimeUtils.To3DAttributes(transform.position + footstepOffset));

            // Optionally you could set parameters here instead of using the supplied FMOD parameter script
            // See the helper function below named "SetParameter" by Liam de Koster-Kjaer
            SetParameter(footstepEvent, "concrete", concrete);
            SetParameter(footstepEvent, "wood", wood);

            // Play and release one-shot instance
            footstepEvent.start();
            footstepEvent.release();
        }
    }

    // Set an FMOD parameter 
    void SetParameter(FMOD.Studio.EventInstance e, string name, float value)
    {
        FMOD.Studio.ParameterInstance parameter;

        e.getParameter(name, out parameter);
        parameter.setValue(value);
    }

    // Set surface variables while touching
    void OnCollisionStay(Collision surface)
    {
        switch (surface.gameObject.tag)
        {
            case "Concrete":
                concrete = 1f;
                break;
            case "Wood":
                wood = 1f;
                break;
        }
    }

    // Zero out surface variables when no longer touching
    void OnCollisionExit(Collision surface)
    {
        switch (surface.gameObject.tag)
        {
            case "Concrete":
                concrete = 0f;
                break;
            case "Wood":
                wood = 0f;
                break;
        }
    }
}

Because my game only has a simple capsule-collider-based player, it makes sense to have the footsteps based on additions of the player’s movement (velocity.magnitude) over time as seen in the UpdateStepCycle() function. But if you have a third-person game, or if your player can see other character models walking around, you definitely want to set up PlayFootStepEvent() to fire on animation keyframes! To be precise, put your Event keyframe on your animation timeline at the same time that the character’s foot touches the ground.

I have used a switch statement to set the parameters based on the tag of the collider the player is touching, but this is probably not entirely bug-proof if you have a dynamic environment where you are disabling colliders.

There are so many ways for you to set up the surface parameter logic for footsteps, you could: check the material name of the colliding object, the pixel colour of the texture; or, for super-high accuracy, use raycasts for each foot! But I find the simplest method to setup and fine-tune footstep surfaces is in using trigger volumes with the FMOD-supplied FMODStudioParameterTrigger script.

The ParameterTrigger script lets you tweak every parameter of a specific StudioEventEmitter with a few clicks in the Unity inspector and it uses all the same default events as a StudioEmitter script. But because it needs to point at an existing StudioEventEmitter in the scene, you’ll have to scrap the instance setup in my footsteps script – instead reference the existing Emitter and use the play function. You’ll have more game objects in your scene with this method but a lot less code logic to worry about.

That’s it for now, I hope this tutorial has helped you to think up some other cool ways to use FMOD Multi Instruments and Parameters! In the final part of this series we will be setting up the Google Resonance plugin and implementing more spatial layouts based on sight-impaired considerations that I’ve gathered from a few awesome research studies. Now that we all understand the basics of FMOD Studio it should be a little more streamlined! Until next time.

 

Viewing all 1620 articles
Browse latest View live