Creating your first Headjack Template

Introduction

This page will teach you how to make a very basic Template for the Headjack platform, that just streams a 360 video. We will then expand this Template to show a very basic menu and make it interactive.

Setting up the Empty Template Project

The empty template project contains most of the necessary (VR) plugins and a complete (compiled) Headjack codebase, to build Headjack Apps for both Windows (64-bit) and Android without the need for Headjack cloud building. This allows for rapid prototyping of a template or app. However, some plugins essential to Headjack cannot be shipped inside template projects, as their licenses do not allow it. So this section describes how to setup the project in Unity to quickly start prototyping templates or building your own apps with existing templates.

Note
It is recommended to use the latest version of Unity (v5.5.2 as of writing) when creating Headjack templates, as that is likely the version Headjack uses to cloudbuild the template

First you need the empty project, which you can download from https://d224lglrmqpadc.cloudfront.net/Versions/headjack_plugin_latest.zip After extracting that zip package and opening the project in Unity, you are presented with an empty Unity scene.

Note
It is recommended to start building your template from a working template in the Headjack template marketplace (https://app.headjack.io/#/templates/template-marketplace/list). This setup applies to those templates as well.

We have included a convenient Editor window with Headjack to aid in the setup process. Open it with the menu option Headjack/Settings.

HeadjackSettingsMenuItem.png
HJSettingsNoPlugins.png

The top of the Headjack Settings window shows the used version of the Headjack plugin (and whether this is the latest version). In case a new version is available, update the template project by clicking "Download latest" Then the window shows any missing plugins. For Headjack to work, the Steam VR and ViveSoftware MediaDecoder plugins need to be separately installed. We have added handy buttons to easily download these plugins. Get Steam VR from the Unity Asset Store by downloading and importing the (free) SteamVR Plugin package.

SteamVRImportPackage.png
Note
Headjack Settings displays a recommended version for required plugins. The recommended version was used when developing Headjack, newer versions will likely work just fine.

Get MediaDecoder from the Unity Asset Store by downloading and importing the (free) MediaDecoder package.

ImportMediaDecoder.png

MediaDecoder requires the FFmpeg library, so click the button to download it and extract that package to /Assets/MediaDecoder/Plugins/x86_64.

ExtractFFmpeg.png

If everything went correctly, you should now see the message "External plugins ok!"

Now the Headjack Plugin is almost ready to be used. However, if you see the error message "This version of headjack does not support ...", the build platform in Unity needs to be changed to one supported by Headjack.

WrongBuildPlatform.png

Select File/Build Settings... and change build target to Windows x86_64 or Android

x64build.png
Note
The platforms that currently support local building with Headjack are Windows (64-bit, all VR APIs) and Android (Cardboard and Oculus VR APIs). iOS is only supported by cloud building through Headjack. Other platforms will follow later.

With a supported build target and all plugins imported, Headjack Settings will show a few more options:

HeadjackSettings.png

The App ID and AUTH Key fields are required and identify the app being tested/build locally to the Headjack CMS. The App ID and AUTH Key can be found on the Edit app page on Headjack. So create an app on Headjack at https://app.headjack.co/#/apps/add, fill in the basic information and click Apply. Now you will be able to find the App ID and AUTH Key at the bottom of the page:

AppIDCMS.png

Copy those values into the appropriate fields of Headjack Settings in Unity and click Apply Settings:

AppIDfilled.png

Now you are ready to start creating and testing your first template. Before we go on let's see what the other options do in Headjack Settings:

Note
Do not forget to match the Unity Build Target with the Virtual Reality API and VR Device selected in Headjack Settings

Making the simplest Template

Now that you have a template project up and running, let's actually create a template.

Warning
For performance and consistency, Unity settings set in a Template are ignored by Headjack when cloud building an app. The Unity settings of the Empty Template project are the settings used by Headjack when building an app.

Headjack requires that all the assets your template uses are in the Assets/Template folder. Other folders are not guaranteed to be included when exporting a template project or are deleted during the cloud build process (e.g. Assets/Headjack). The only exception is the special Assets/StreamingAssets folder, which is included in template exports for streaming assets (see Unity documentation). Create a new Unity scene called App.unity in the Assets/Template folder.

Note
App.unity in the Assets/Template folder is always the first scene loaded by any app cloud build by Headjack.
Warning
Headjack builds all Unity scenes in the Template folder into the app. So if an app build by Headjack turns out very large, look into removing unused scenes and assets from the Template.

Create an empty asset in the scene that will reference our main script, by clicking GameObject/Create Empty, and name it PlayVideoAsset. Click on PlayVideoAsset in the Hierarchy and Click the large Add Component button in the Inspector. Add a New Script, C Sharp language, named PlayVideoScript. Double click the script to open an editor and start editing PlayVideoScript. under the existing "using" statements at the top of the script, add

using Headjack;

This will make it easier to call Headjack API functions.

Note
With the exception of the VideoPlayer object, all Headjack API function are static functions that can be called from any script (you don't need a reference to an initialized object)

Now we initialize the app by calling App.Initialize(OnEnd, bool, bool). This function downloads the app data from the Headjack servers, so the app is up to date when started. The function also optionally initializes the VR camera (recommended). We initialize the app in the script's Start function so it runs immediately when the app starts:

// Use this for initialization
void Start () {
// Download data from server, like video and thumbnail information for this app
// also creates the camera (autoCreateCamera = true) and a cardboard app starts in VR mode (cardboardStereoMode = true)
App.Initialize(Initialized, true, true);
}
void Initialized(bool success, string error)
{
}

We also added the Initialized function with two parameters and passed that function to App.Initialize(OnEnd, bool, bool) as the first parameter. This first parameter is an OnEnd event that is triggered when App.Initialize is finished. Now we implement the Initialized function and use the app data downloaded from the server to stream the first project in this app's project list.

// Called after app initialization
void Initialized(bool success, string error)
{
// Only start streaming a project if the app is successfully initialized
if (success)
{
// get the first project in the projects list (if there is a first project)
if (App.GetProjects().Length > 0)
{
// ID of the first project
string firstProjectID = App.GetProjects()[0];
// stream the first project (over wifi only)
App.Play(firstProjectID, true, true, delegate (bool vidSuc, string vidErr)
{
// this is an inline delegate that can be used instead of passing a function name as OnEnd
// this is called once when the video is finished playing
App.ShowMessage("You just watched the first project", 5);
});
}
else
{
// tell the user that this app contains no projects
App.ShowMessage("This app contains no videos to show", 5);
}
}
else
{
// show the error message (as 5 second popup), because App.Initialize failed
App.ShowMessage(error, 5);
}
}

Save the script and preview the scene in the editor. If you have entered a valid App ID and AUTH Key in the Headjack Settings (see previous section) and that app has atleast one project with a video attached, you should now see that video in 360 streamed to your machine.

You can also see a VideoPlayer asset and "DontDestroyOnLoad" item added to the scene Hierarchy, with the VR Camera objects.

Note
Any asset in a Unity scene under the section "DontDestroyOnLoad" is not destroyed when changing Unity scenes at runtime

You should never have to directly interact with these objects, instead use the API functions in Headjack.App. When the video is finished, you should see a popup message saying "You just watched the first project".

If you wanted to use this Template to build an app with the Headjack platform, you can either upload it to Headjack and cloud build or build locally. To use cloud building, first create a Template zip file by clicking the menu option Headjack/Export Project as Template On the Headjack platform, you can now upload this zip file as a template (either private or public) here: https://app.headjack.io/#/templates/my-templates/add and then select this Template for an app to build the app with this Template.

To build locally, please follow the section below, titled "Building Headjack apps locally".

In the next section, we will expand this simple Template with a really simple menu and buttons to download and play a project.

Making a simple menu

The simplest Template you just created in the previous section is not very flexible and only supports a single project. Let's make use of Headjack's simple API to make the Template more flexible and interactive.

We begin this Template in much the same way as in the previous section: in an empty App.unity scene (in Assets/Template), create an Empty asset and add a C# script (called Startup) beginning with:

using System.Collections;
using Headjack;
public class Startup : MonoBehaviour {
// only instance of this startup script, usable by project menu items
public static Startup instance;
// Use this for initialization
void Start()
{
instance = this;
// Download data from server, like video and thumbnail information for this app
// also creates the camera (autoCreateCamera = true) and a cardboard app starts in VR mode (cardboardStereoMode = true)
App.Initialize(delegate(bool succ, string e)
{
if (succ)
{
// after downloading app info, download and load all project thumbnails to show in menu
App.DownloadAllTextures(Initialized);
}
else
{
App.ShowMessage("Internet Connection required when first launching app");
}
}, true, true);
}
void Initialized(bool success, string error)
{
}
// a static method to play a project, used by project menu items to start playing a project
// this static method disables the menu before playing the project
public void Play(string projectId, bool stream)
{
}
}

Besides some placeholder code, the main difference to notice here is that we (down)load the project thumbnails after initialization, so we can show those thumbnails in our menu. Now that we have loaded the project information and thumbnails, we have to initialize the menu, but before we can do that, we have to create the menu elements. Because we are going to use the same menu elements (buttons, text, thumbnail) for each project, we're going to create a Unity prefab that holds all these elements:

Create an Empty holding all elements and the script we are going to create, call it "Project".

Project.png

As a child of Project, add:

We need a Material for the thumbnail Quad, to show the thumbnail and another one for the ButtonBackground Quad, to distinguish the buttons from the background:

Now we're going to create the script that each menu item prefab will use to respond to the buttons and change the title and thumbnail according to the project. Create a C# script called "ProjectMenu", with this content:

using System.Collections;
using Headjack;
public class ProjectMenu : MonoBehaviour {
// public fields preset in prefab for manipulation of this menu item
public TextMesh titleText; // to change project title
public MeshRenderer thumbnailRenderer; // to change project thumbnail
public MeshCollider downloadButtonCollider; // to detect download button press
public MeshCollider playButtonCollider; // to detect play button press
public TextMesh downloadButtonText; // to change download button text based on download status
// Project ID corresponding to this menu item
string projectId;
// download state
enum DownloadState
{
Available, // Project has not been downloaded yet
Downloaded, // Project has been downloaded
Downloading, // Project is currently downloading
};
// this function is called to set project ID of this project menu item
public void SetProjectId(string pID)
{
projectId = pID;
// update project title
titleText.text = App.GetTitle(projectId);
// update project thumbnail
thumbnailRenderer.material.mainTexture = App.GetImage(projectId);
// update download button text based on local file/download state
UpdateDownloadState();
}
// sets text of download button according to download state and returns that state
DownloadState UpdateDownloadState()
{
// Local files exist already, change download button to delete
if (App.GotFiles(projectId))
{
downloadButtonText.text = "Delete";
return DownloadState.Downloaded;
}
// Project is currently downloading, change download button to cancel
if (App.ProjectIsDownloading(projectId))
{
downloadButtonText.text = "Cancel";
return DownloadState.Downloading;
}
// Project is not downloading or downloaded, so ready to download
downloadButtonText.text = "Download";
return DownloadState.Available;
}
// Update is called once per frame
void Update () {
// every frame, check whether a confirm input has been registered
if (VRInput.Confirm.Pressed && projectId != null)
{
// confirm has been pressed on any of the VR input devices
// check if one of the buttons of this menu item was selected
if (App.IsCrosshairHit(downloadButtonCollider))
{
// download button was pressed, take action based on download state
DownloadState dlState = UpdateDownloadState();
switch (dlState)
{
case DownloadState.Downloaded:
// delete downloaded project
App.Delete(projectId);
break;
case DownloadState.Downloading:
// cancel downloading project
App.Cancel(projectId);
break;
case DownloadState.Available:
default:
// start project download
App.Download(projectId, true, delegate(bool succ, string e)
{
// download finished, update download button
UpdateDownloadState();
// if download failed, show message to user (for 5 seconds)
if (!succ)
{
App.ShowMessage("Download failed: " + e, 5);
}
});
break;
}
// update download button text after the above action
UpdateDownloadState();
}
else if (App.IsCrosshairHit(playButtonCollider))
{
// play button was pressed, play the downloaded video if the project is downloaded,
// otherwise, stream the project
if (UpdateDownloadState() == DownloadState.Downloaded)
{
Startup.instance.Play(projectId, false);
}
else
{
Startup.instance.Play(projectId, true);
}
}
}
}
}

Some notes on the ProjectMenu script:

Note
When a project is streamed, the quality of the video depends on the quality of the internet connection. A downloaded project always displays at the highest resolution possible for the device.
When a project is streamed, spatial audio is not supported and the audio track in the video file is played

Now to finish the menu item prefab, attach the ProjectMenu script to the Project empty asset and assign the references in the script as follows, by dragging the appropriate asset into the fields of the Project Menu (Script):

Now to create the prefab, drag the Project asset into the Assets window and a Project.prefab asset should be created (and the assets in the Scene Hierarchy should turn blue).

FullTemplate.png
Structure of the Template before deleting Project prefab

We can now delete the Project asset and all its subassets from the Scene Hierarchy, as we will be creating the menu one item at a time using the Startup script and the Project prefab.

Note
Deleting the assets a prefab is based on from the Scene Hierarchy does not delete or change the prefab.

We should now be left with a Scene with only the Startup asset in it. We will create one more Empty asset, call it "Menu" and change its position (X = 0, Y = 0, Z = 1.5).

Menu.png

The Menu asset will be used as a parent for the menu items we are going to create for each project, so we can turn the menu off easily by disabling the Menu asset.

Lastly, we need to expand the Startup script to create the menu of projects when the app has finished initializing and create a custom Play function to disable this menu before playing a video. This is complete Startup script:

using System.Collections;
using Headjack;
public class Startup : MonoBehaviour {
// only instance of this startup script, usable by project menu items
public static Startup instance;
// Menu GameObject containing the entire menu, for convenient menu disabling
public GameObject menu;
// Prefab of a project menu item
public GameObject menuItemPrefab;
// true when currently playing a video
bool playing;
// Use this for initialization
void Start()
{
instance = this;
// Download data from server, like video and thumbnail information for this app
// also creates the camera (autoCreateCamera = true) and a cardboard app starts in VR mode (cardboardStereoMode = true)
App.Initialize(delegate(bool succ, string e)
{
if (succ)
{
// after downloading app info, download and load all project thumbnails to show in menu
App.DownloadAllTextures(Initialized);
}
else
{
App.ShowMessage("Internet Connection required when first launching app");
}
}, true, true);
}
void Initialized(bool success, string error)
{
// if (down)loading all project thumbnails failed, show a message to the user and don't load menu
if (!success)
{
App.ShowMessage("(Down)Loading project thumbnails failed: " + error, 5);
return;
}
// show crosshair in menu view so menu buttons can be selected by gaze
App.ShowCrosshair = true;
// all project metadata downloaded and loaded, so construct a menu item for each project
Vector3 currentMenuPos = menu.transform.position; // position of current menu item, to place menu items on a horizontal line
GameObject currentMenuItem; // holds current menu item
foreach(string currentProjectId in App.GetProjects())
{
// instantiate prefab (i.e. create a clone of) of project menu item
currentMenuItem = (GameObject)Instantiate(menuItemPrefab, menu.transform, false);
// set position of menu item
currentMenuItem.transform.position = currentMenuPos;
currentMenuPos.x += 0.65f; // move current menu position so next menu item is placed correctly
// set project id of this newly created menu item (also sets projet title and thumbnail)
currentMenuItem.GetComponent<ProjectMenu>().SetProjectId(currentProjectId);
}
}
// a static method to play a project, used by project menu items to start playing a project
// this static method disables the menu before playing the project
public void Play(string projectId, bool stream)
{
// disable menu before starting video
App.ShowCrosshair = false;
menu.SetActive(false);
playing = true;
// play video (over wifi only)
App.Play(projectId, stream, true, delegate (bool succ, string e)
{
// when video is finished playing, show menu
App.ShowCrosshair = true;
playing = false;
App.DestroyVideoPlayer();
menu.SetActive(true);
});
}
// Update is called once per frame
void Update ()
{
// when playing a video, pressing the universal back button returns the user to menu
if (playing && VRInput.Back.Pressed)
{
App.ShowCrosshair = true;
playing = false;
App.DestroyVideoPlayer();
menu.SetActive(true);
}
}
}

Some notes on the Startup script:

All that is left to do is to update the references to Menu and the Project prefab:

To test this finished Template, play the Scene in the Unity Editor and you'll see your menu in action with the images and videos from your app on the Headjack CMS.

To cloud build a Headjack app with this template, create a Template zip file with menu option Headjack/Export Project as Template. Upload it to Headjack here: https://app.headjack.io/#/templates/my-templates/add And select the uploaded Template when building a new Headjack app.

To build this app locally, follow the section below.

Building Headjack apps locally

The latest Headjack Unity project supports local building of apps for Windows (Oculus and OpenVR APIs) and Android (Oculus and Cardboard APIs). Other platforms will either receive support later (e.g. Daydream) or are only supported by Headjack using cloud building (iOS currently).

Different build configurations require different additional steps to build locally, so the sections below describe the required steps for the different configurations. After applying the changes from the appropriate section(s) below, build your app in the usual way in Unity: press the Build button on the File/Build Settings... window

All builds

Whatever platform you build for, pay attention to the following settings:

All Windows builds

Due to an oddity in the ordering of VR SDKs, running Headjack in the Editor requires a different primary Virtual Reality SDK from Windows builds:

All Android builds

When building for Android, set the following settings in Player Settings (Android):

Android store builds

Whether building an Android app for the Play Store or the Oculus Gear VR Store, set a valid keystore at Player Settings (Android)/Publishing Settings. For instructions on how to generate a keystore, follow the instructions at https://headjack.io/tutorial/publish-google-cardboard-app-android-google-play/

Gear VR local builds

Local Gear VR builds (not distributed through the store) require a signature file for each phone running the build. Use the following guide to create the osig file for each phone you intent to install the app to: https://headjack.io/tutorial/distribute-gear-vr-app-outside-oculus-store/ Then place the generated osig files in the following directory of your Unity project: Assets/Plugins/Android/assets

Gear VR store builds

Gear VR builds for the Oculus Store require several changes to pass validation:

Oculus Rift store builds

Oculus Rift Windows (64-bit) builds for the Oculus Store require an Oculus App ID and need a small fix to pass validation: