NowSo I needed to create a Hololens 2 App and deploy it to an actual Hololens 2 that I had with me for development, AND I needed to save a copy of the compiled app that could be copied onto a Hololens 2 that my client had in another office. It was a very convoluted process so I thought I would document it now so I wouldn’t forget it for next time round.

 

Before you start make sure you have the following

Unity 2019.4.4f1 

MRTK 2.4.0 

Visual Studio 2019

 

The Best Tutorial on Set Up

I could go through and tell you how to create a Unity file that will run on the Hololens2, but really, just follow this one that Microsoft put out. It is the best.

https://docs.microsoft.com/en-us/windows/mixed-reality/develop/unity/tutorials/mr-learning-base-01

Just go through that page and you have set up your basic Unity MRTK file that compiles to the Hololens 2 through Visual Studio.

 

HOW TO CREATE A FILE TO SEND TO SOMEONE TO INSTALL

This is where is gets fiddly and you need to set up certificates and stuff. So…. let’s get started

 

In Unity, open your Player Settings File > Build Settings > Player Settings.

In the Player Settings Window Expand the Publishing Settings

Scroll down to  the Certificate section and click the Create… button

 

 

A pop up box will appear and ask you to fill in a Publisher (I usually put in either my client name or my Sole trader name), and a password. Do this and then click Create

 

 

Now go back to Visual Studio and in the Solution Explorer window expand your app and locate the Package.appxmanifest file. Click on it to open it.

 

 

Click on the Packaging heading and then click on the Choose Certificate button

 

 

Click on the Select from file… button

 

 

Navigate to  the Project Assets folder (make sure it’s not the Builds Asset folder!) and select the WSATestCertificate.pfx file

 

 

Now remember in Unity we gave the certificate a password, you’ll need to enter that now and press OK

 

Now click on View Full Certificate

 

 

And click on Install Certificate

 

Choose the Current User radio button and click Next

 

 

Choose the Place all certificates in the following store radio button and click Browse

 

 

Choose the Trusted Root Certification Authorities and click OK

 

 

Click Next

 

 

Click Finish

 

 

You’ll get a Security Warning. Click Yes

 

 

Then you’ll get a message that the Import Was Successful. Hit OK

 

 

And click OK

 

 

Now right click on your app in the Solution Explorer Your App > Publish > Create App Package

 

 

Select Distribution Method. Choose the sideloading radio button and click Next

 

 

Make sure the radio button Yes use the current certificate is selected and click Next

 

 

Hololens2 needs ARM64 so make sure that checkbox is selected also and hit Create

 

 

Navigate to the folder in Windows Explorer that you gave as your file path to save the packages, and you can send these to your tester to save onto their Hololens 2.

 

 

I have another blog post that shows you how to copy it onto your Hololens 2 here

 

 

using UnityEngine;
using UnityEngine.Windows.Speech;

public class SpeechRecogniser: MonoBehaviour
{
protected DictationRecognizer dictationRecognizer;

void Start()
{

}

// CALL THIS FUNCTION WHEN YOU WANT TO START LISTENING TO DICTATION
public void startDictationRequest(){

// THIS IS REALLY IMPORTANT!!
// If you are using the MRTK and have phrase recognition running, then you can't use both at once,
// so you need to turn it off with the following line
if (PhraseRecognitionSystem.Status == SpeechSystemStatus.Running)
{
PhraseRecognitionSystem.Shutdown();
}

StartDictationEngine();

}

// CALL THIS FUNCTION WHEN YOU WANT TO STOP LISTENING TO DICTATION
public void stopDictationRequest(){

// THIS IS REALLY IMPORTANT!!
// If you are using the MRTK and turned off phrase recognition to enable dictation
// you need to turn it back ON again with the following line
if (PhraseRecognitionSystem.Status != SpeechSystemStatus.Running)
{
PhraseRecognitionSystem.Restart();
}

CloseDictationEngine();
}

private void DictationRecognizer_OnDictationHypothesis(string text)
{

// So in here is the hypothesised text. It's quick, like as fast as it's spoken you'll get the string passed in here
// but it's slightly less accurate. So test what you get and see if it's close enough,
// otherwise do your checking for your phrases in the OnDictationResult method

if(text.Contains("your required phrase here")){
do something here
}
}

private void DictationRecognizer_OnDictationComplete(DictationCompletionCause completionCause)
{
switch (completionCause)
{
case DictationCompletionCause.TimeoutExceeded:
case DictationCompletionCause.PauseLimitExceeded:
case DictationCompletionCause.Canceled:
case DictationCompletionCause.Complete:
// Restart required
CloseDictationEngine();
StartDictationEngine();
break;
case DictationCompletionCause.UnknownError:
case DictationCompletionCause.AudioQualityFailure:
case DictationCompletionCause.MicrophoneUnavailable:
case DictationCompletionCause.NetworkFailure:
// Error
CloseDictationEngine();
break;
}
}

private void DictationRecognizer_OnDictationResult(string text, ConfidenceLevel confidence)
{

// Debug.Log("Dictation result: " + text);
if(text.Contains("in")){
messageText.text = "RESULT CONTAINS IN : " + text;
}else{
// Debug.Log("RESULT does not CONTAIN IN!!!");
}
}
private void DictationRecognizer_OnDictationError(string error, int hresult)
{
messageText.text = "Dictation error : " + error;
Debug.Log("Dictation error: " + error);
}
private void OnApplicationQuit()
{
CloseDictationEngine();
}
private void StartDictationEngine()
{
Debug.Log("StartDictationEngine");
dictationRecognizer = new DictationRecognizer();
dictationRecognizer.DictationHypothesis += DictationRecognizer_OnDictationHypothesis;
dictationRecognizer.DictationResult += DictationRecognizer_OnDictationResult;
dictationRecognizer.DictationComplete += DictationRecognizer_OnDictationComplete;
dictationRecognizer.DictationError += DictationRecognizer_OnDictationError;
dictationRecognizer.Start();
}
public void CloseDictationEngine()
{
if (dictationRecognizer != null)
{
dictationRecognizer.DictationHypothesis -= DictationRecognizer_OnDictationHypothesis;
dictationRecognizer.DictationComplete -= DictationRecognizer_OnDictationComplete;
dictationRecognizer.DictationResult -= DictationRecognizer_OnDictationResult;
dictationRecognizer.DictationError -= DictationRecognizer_OnDictationError;
if (dictationRecognizer.Status == SpeechSystemStatus.Running)
{
dictationRecognizer.Stop();
}
dictationRecognizer.Dispose();
}
}

Right, so classes have a number of methods in them and it can get a bit confusing as to when each of them are called, especially when you require a very specific sequence of events to occur.

So let’s say that you are instantiating a prefab from a class method.

 yourPrefab yPre = Instantiate(yourPrefab, transform.position, transform.rotation);
 
.SpawnAsteroid();
        ast.gameObject.name = "Asteroid_" + i.ToString("00");
        // Find a good location for the Asteroid to spawn
        Vector3 pos;
        do
        {
            pos = ScreenBounds.RANDOM_ON_SCREEN_LOC;
        } while ((pos - PlayerShip.POSITION).magnitude < MIN_ASTEROID_DIST_FROM_PLAYER_SHIP);

        ast.transform.position = pos;
        ast.size = asteroidsSO.initialSize;

Scriptable Objects have 2 major uses

  1. Saving and storing data during an editor session
  2. Saving data as an asset in our project for use at runtime

Things to Note

  1. In the editor we can save data to the SO during edit and runtime
  2. In a deployed build we can only read the data from the SO, we cannot save to it. – so no good for saving game state data.

So what’s good about Scriptable Objects

So you can make your scriptable object an Asset and create one in your Project panel with the “Create” menu. You can also define the name of your scriptable object as it appears in the create menu.

Adding your Scriptable Object to the Unity “Create” menu

 [CreateAssetMenu(menuName = "Scriptable Objects/YourObjectName", fileName = "YourObjectName.asset")]

Now once you have created your scriptable object asset, it’s public properties will be available in the inspector simply by selecting it in your project panel. You don’t need to attach it to a scene gameobject in order to be able to view and edit the data.

Why not just use a class?

Well yes you could, but using a scriptable object to store unchanging game data is probably the most efficient way. It creates just one set of data that remains constant, and everything in your file can access this one copy, rather than creating an instance of the data for every gameobject that uses it.

 

Serialization of Scriptable Objects

Two important attributes to know about

System.Serializable

System.SerializeField

Classes and struct need to be [Serializable]. All public fields are then serialized. Add it above the class definition.

[System.Serializable]
public class MyScriptableObject : ScriptableObject

In Unity, the [SerializeField]  attribute allows you to have private script variables that are exposed in the Inspector. This will let you set the values in the editor without giving access to the variable from other scripts.

 

So what does this word “static” do to variables and methods? Well a couple of things.

  1. static variables and methods that are public are accessible from anywhere without having to first reference or instantiate the class that they are in. Note though that if they are private, they can not.
  2. They make variables class variables, rather than instance variables. This comes in handy with games especially when you are creating multiple enemies or bullets or obstacles and you want to keep a total count of them. Rather than each instance of the enemy/bullet/obstacle keeping a count, the class as a whole keeps count.
using UnityEngine;
using System.Collections;


public class Obstacle
{
    //Static variables are shared across all instances of a class.
    public static int obstacleCount = 0;

    public Obstacle()
    {

    }

    static public Obstacle SpawnObstacle()
    {
        //Increment the static variable to know how many
        //objects of this class have been created.
        obstacleCount++;

        GameObject oGO = Instantiate<GameObject>(GameManager.ObstacleSO.GetObstaclePrefab());
        Obstacle obst = oGO.GetComponent<Obstacle>();
        return obst;
    }
}

 

Now one of the cool things about having a static method is that you can reference them directly and I’ll show you the code for that here.

using UnityEngine;
using System.Collections;

public class GameManager
{
    void Start()
    {
        for (int i = 0; i < 3; i++)
        {
            Obstacle obst = Obstacle.SpawnObstacle();
            Vector3 pos = ScreenBounds.RANDOM_ON_SCREEN_LOC;
            obst.transform.position = pos;
        }

        //You can access a static variable by using the class name
        //and the dot operator.
        int x = Obstacle.obstacleCount;
    }
}