Unreveal

A few years back I was giving a talk at a Unity programmer event and wanted something more appealing than the standard "Here's a PowerPoint with some screenshots included". I decided to quickly plug the (rather splendid) JavaScript-based reveal.js presentation engine into Unity, allowing me to combine the presentation with actual in-engine interactive experiments.

It worked quite nicely.

I've now moved across to Unreal Engine though, and it's time to rebuild this.

As we're going to be building a C++ plugin from source we're going to need to have a compiler set up, for Windows this means Visual Studio 2015. It is possible though that you may be able to use the Visual Studio Community Edition for free but you'd have to check the licensing terms to be sure, I'm not going to summarise them here as I don't want to be slapped by lawyers for giving incorrect licensing advice.

This isn't intended as a beginner tutorial and so I won't be going into full details on how to use UE4's interface. You should have a good grasp of editing Blueprints before trying this, and I'll happily be adding things like reroute nodes, comments, and sequences without explaining what they are.

It's also not intended to teach you HTML, CSS, and JavaScript. I'll briefly describe what I'm doing there but the focus is strongly on the UE4 side of things, and also on getting something useful at the end of it.

Basic Approach

The basic approach here is to use Unreal's HUD functionality with a widget to display a web page hosting the Reveal.JS presentation. There needs to be a way to route messages through from the UE4 side of things into the JavaScript.

UE4 comes with a web browser widget which could be used to display the presentation but there are a few problems with it.

Firstly it's only an Experimental feature so can't really be relied on too much. There's no way to serve content easily from a local directory without needing to write file handling code in C++, and while it provides a way to call JS code from UE4 there's no way to do the opposite.

Instead of trying to solve these I'm going with the Open Source BLUI plugin, which does allow local content to be served and supports JS to UE4 calls. I've used this before and although the author is busy with college at the moment and so it's not under heavy development it still works really nicely.

There are other possibilities to be used, such as the commercial Coherent UI or even incorporating non-UE4 systems such EA's Webkit, but I'll be sticking to BLUI as it's free and does what I need for this project.

Creating the project

There's nothing we really need from a starter UE4 project and so we can start this one with a blank Blueprint project with no starter content.

Creating a new project

As I'm going to be using Github I'll also be using the standard Unreal Engine .gitignore.

A Simple Fullscreen HUD

Widget Blueprint

For the combined presentation system to work it'll need to display the presentation text and graphics over an Unreal Engine rendered scene, and for this we can use UE4's UMG system.

This is more commonly used to add overlays over games, showing the player's health and so forth, but we're going to be using it for the presentation.

The first thing we're going to create is a Widget Blueprint. This is a special type of Blueprint which describes all of the elements, such as text or graphics, which go to build up a HUD.

Right click in the Content Browser window and choose "User Interface > Widget Blueprint". Sticking to Allar's naming conventions call the new Widget Blueprint WBP_UnrevealHUD.

Double click on the WBP_UnrealHUD to open the Widget Blueprint Editor.

Widget Blueprint Editor

Eventually this is where we'll be putting the display for our presentation but for now we'll just display a header so we can quickly get the rest of the HUD system sketched out.

Choose "Text" from the Common area of the Palette to the left and drag it onto the large editor window, this will add a new text item saying "Text Block".

To make sure the HUD fills the screen properly we'll centre this at the top of the screen. Click on the little down arrow next to "Anchors", this allows us to choose how our object will be anchored onto the screen- we want it to fill the top part and so choose the icon where it fills the top of the screen, as shown below.

Setting anchor

Now we can set the size and position of our object. Put "Offset Left" to 0 to make the text flush against the left-hand side, "Position Y" to 0 to make it flush against the top, "Offset Right" to 0 to make it flush against the right.

To make the text itself centred click the middle icon in "Justification", the one where all the boxes are in the middle and which says "Align Text Center" when you hover over it. This pushes the text into the centre rather than keeping it all down the left.

I'm also setting the "Text" in Details to read "Unreveal HUD Placeholder", but this isn't really needed.

Unreveal HUD Placeholder Widget

HUD Blueprint

While the Widget Blueprint defines the appearance of the HUD we need/ another, more standard, type of Blueprint to describe it's behaviour and to interface it with the rest of our system.

Create a new Blueprint and rather than choosing from the list of common classes enter "HUD" in the "All Classes" search field, click on "HUD" from the list that's displayed and press "Select" to create the HUD. We need to do this as UE4 only offers the most commonly created Blueprints in the list so less used items, such as HUD, require us to do a bit of digging.

Name the newly created Blueprint BP_UnrevealHUD.

Creating a HUD BP

Open the standard Blueprint editor by double clicking on the BP_UnrevealHUD item and go to the Event Graph.

Add a "Create Widget" node and connect it to Begin Play, setting "Class" to WBP_Unreveal. This will mean that as the HUD is created at the start of play it'l create a copy of the widget we created above.

Drag off the return value of the Create Widget and choose Add to Viewport. This means that once the widget is created it'll be added to the player's screen, making it visible and usable.

GameMode

There are a few different ways to choose which HUD is being used in-game but the simplest one is to specify it in a GameMode.

Create another new Blueprint, this time using "Game Mode" from the Common Classes list. Name it BP_UnrevealGameMode.

Double click on it to open the Blueprint Editor. All we need to do here is to go to the Classes section of the Details panel and choose BP_UnrevealHUD for the "HUD Class" setting.

Creating a GameMode

To make UE4 use this newly-created GameMode for our project go to the Edit menu at the top and choose "Project Settings" to open up the settings editor. Choose the "Maps & Modes" tab on the left of the screen and under "Default Modes" select BP_UnrevealGameMode for "Default GameMode".

Setting GameMode

Back in the main editor press Play and, if all is working, you should see the text label at the top of the screen.

This has been a lot of work for getting a simple label but it's also created all of the basic Blueprint structures we'll be using for the rest of this project and so it'll be worth it in the long run.

Installing BLUI

As I mentioned above for the actual web browser I'm going to be using the BLUI as while it's not been updated recently it does work, and is free. It shouldn't be too difficult to switch to an alternative system later on if this hits problems, or if the inbuilt UE4 web widget gets a few more useful features such as being able to call Blueprint code from the JavaScript environment.

Anyway- back to BLUI. The installation is documented on the BLUI wiki and so I'll only briefly cover it here. BLUI does work on other OSes, I've had it running on my Mac and I know people who've run it on Linux, but I'm just going to cover Windows here at the moment. The installation is documented on the BLUI wiki and so I'll only briefly cover it here.

My build file, Source\ue4_unreveal.Build.cs, with the added line, reads:

// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;

public class ue4_unreveal : ModuleRules
{
	public ue4_unreveal(TargetInfo Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
		PublicDependencyModuleNames.Add("Blu");

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}

Missing modules

Once the project has opened we should be able to go to the "Edit" menu, and choose "Plugins" to list all of the plugins enabled.

At the bottom of the list on the left should now be a "Project" section, and inside this a "UI" category. Clicking on this should show that yes, BLUI is indeed installed and enabled.

Checking BLUI is enabled

Using BLUI for the HUD

As BLUI is now installed we can take the next step and look at getting it set up so we can display a web page as our HUD. This involves a fair bit of setup and so let's open the BP_UnrevealHUD Blueprint and start with that.

As there's a lot of 'stand alone' code here I'm going to create a function called SetupBLUI, so I add that to the Functions list and start editing it.

Each BLUI view is configured and controlled using a special BLUI object and so to create that we right-click in the function's workspace and choose "Create BluEye", and I'm as confused about why it swaps from BLUI to BluEye as any of you.

The first thing we'll do with this object once it's created is store it in a variable so we're able to manipulate it later. If you right-click on the output from the "BluEye" node and choose "Promote to Variable" it'll create a variable of the right type, we just need to rename it to BluEye as a reminder of what it is.

Created BluEye object

I know from past experience that BLUI can have trouble if you don't give it a default URL before loading it, I've had crashes and such from that, and so I add the "Set Default URL". As we don't have an actual proper URL yet as that's still to be built this just needs a valid URL to go to, I just set it to "http://normalvector.com/tutorials/unreveal/" so I can view an early version of this tutorial while writing this tutorial.

Also in variables there's a "Set Is Transparent", which allows us to control whether we can see through the background of the browser window. As the whole idea of this project is to combine the presentation with the UE4 content we use this node and check the "Is Transparent" box.

The last variable I use is "Set Enabled", which is needed if we're to actually update this browser. This may seem a strange variable but it's useful, for example if you wanted to load a page just for a static texture you could unset this after it's loaded. For this though we just check the "Enabled" box.

As everything is configured I finally add an "Init" node from the BluEye which finalises the configuration and gets it ready to run.

Init BluEye

Going back to the main event graph I add a call to the SetupBLUI function from the "Event BeginPlay" chain.

Calling SetupBlui

We can now think about how we draw the HUD. There's a HUD-specific event node called "Event Receive Draw HUD" which is called when we should draw the HUD, and so we create a version of that to begin with.

In order to work the BLUI system needs to have a special function called when it should update, without it we'd see nothing, and so the first thing we do is add a "Run BLUI Tick" node here.

Next we want to draw the BluEye browser is displaying, which is made available to us as a texture. Right click and create a "Draw Texture" node, there are a lot of parameters but they're pretty simple for our use.

Keep "Target" to "Self", we're in the Blueprint of the HUD that's drawing. The "Texture" needs to be obtained from BluEye so we create a "Get" node for that, and then call "Get Texture" on it- wiring the output into the "Draw Texture"'s Texture input.

"Screen X" and "Screen Y" should remain as 0 so we're drawing in the top-left. To fill the entire display "Screen W" needs to be equal to the width of the screen and so we drag a connector from the "Event Receive Draw HUD" "Size X" to it, and as it's going from integer to float it creates a conversion node automatically. We do the same for "Screen H", dragging a connector from the "Size Y" output.

We want to display the entire texture, and since UV maps are a square from 0,0 to 1,1 that's the space we need to configure here. We set "Texture U" and "Texture V" to zero, and "Texture UWidth" and "Texture VHeight" both to one.

There's no need to change the "Tint Color" from white, we don't want to apply a strange coloured tint to our browser. We do change "Blend Mode" though, setting it to "Translucent" so we'll be able to see through the presentation to our UE4 content beneath.

We ignore all of the scale and rotation settings, they simply don't apply for us. We need a straightforward mapping.

Draw Texture in HUD

Go back to the main editor, press Play, and the entire view is filled with a distorted copy of the tutorial, there's our placeholder text still at the top, and we can't see through the UE4 content through the page.

It's distorted because we're not scaling the browser correctly though, the placeholder's there as we've not removed it, and we can't see through since the Normalvector website doesn't have a transparent background. All of these are fixable.

Webpage in HUD

Resizing the Browser

The next challenge is handling the browser resizes.

BLUI does have a node called "Resize Browser", which is basically what's needed, and so we could get it to work just by calling this each frame with the "Size X" and "Size Y" from the "Event Receive Draw HUD".

This would work, but looking at the code for BluEye I can see it actually does quite a lot when it receives a resize instruction, it'd be wasteful to call it when we don't need to.

So we need to check whether the screen has actually resized and only if it has do we need to actually do the resizing. Back in BP_UnrevealHUD create a new function HandleHUDResize.

In order to check if it's resized we'll need to know what size the HUD now is, and to do this we can add inputs. We could do this in a few ways, as a Vector 2D, as two floats, or as two integers. As we're going to call it from "Event Receive Draw HUD" I'm going to go with what that uses, two integers labelled SizeX and SizeY.

Now we have the new size how do we get the old size? We can pull it out of BluEye and so drag that variable back on to create two "Get" nodes, and then drag off the first for a "Get Width" and a "Get Height" node off the second.

We need to resize if either of these have changed so the first thing to do is create two "Not Equal (Integer)" nodes. We connect "Size X" and "Width" into the first to see if the width has changed, and "Size Y" and "Height" into the second to check on the height.

We need to know if either of these have changed so create an "Or Boolean" node, connecting the outputs of our two "Not Equal (Integer)" nodes to it. The output of this will now be true if the width has changed or the height has changed, which is what we're after.

We can now use this as the Condition for a Branch, which we call from the execution pin of our "Handle Resize" node. We don't have anything to do if the size hasn't changed so can leave False.

If it has resized though then we do need to tell BluEye, so drag that on to create a "Get" node and drag off the pin to create a "Resize Browser" node. Connect our "Size X" input through to "New Width", and "Size Y" to "New Height" to tell it the new size.

Handle Resize

Now we've written this we can go back and call it from our "Event Receive Draw HUD" node in the Event Graph, making sure to connect the "Size X" pins and "Size Y" pins of our function to those of the event.

Calling Handle Resize

If we now press Play we'll see that we have the Normalvector webpage displayed on our HUD but now we've removed the distortion. Even if we resize the window the browser updates properly, keeping everything crisp.

Webpage Without Distortion

Serving Local Web Content

It would be possible to set things up so that users would need to install Reveal.js on a webserver and upload their presentations there, but it's much easier if they can just keep everything in their project directories.

Fortunately BLUI allows us to do this, we just need to create a folder for it and then use blui:// instead of http:// as in our URLs.

Go to the directory your UE4 project lives in, the one with the .uproject file, and create a UI folder. We're going to be keeping all of our HTML, CSS, JavaScript, and other web content in here.

For now open your favourite text editor (I use Atom or WebStorm for my webdev work), copy/paste the following code (Or use the raw version here), and save it as index.html in your UI folder.

<!doctype>
<html>
  <head>
    <title>Simple Overlay</title>
    <style>
      body {
        background-color: transparent;
        color: white;
        text-shadow: 2px 2px 2px rgba(0,0,0, 0.5);
        padding: 4rem;
        text-align: center;
        font-family: sans-serif;
      }
    </style>
  </head>
  <body>
    <h1>Unreveal</h1>
    <p>This is just a piece of placeholder content for Unreveal</p>
  </body>
</html>

Safely back in UE4 we can now go into our BP_UnrevealHUD Blueprint, open the SetupBlui function and change the "Default URL" value to point to our local HTML file by setting it to blui://UI/index.html. We could make this a variable but as HUDs are not things we add directly to the game world it always seems to me that changing them is more trouble than it should be.

When you've compiled and saved pressing Play should now show the contents of the HTML page we've just created, and as the page is designed with a transparent background we can even see the UE4 content through it.

Placeholder HTML in Editor

Removing The UMG Text

We still have the "Unreal HUD Placeholder" text appearing at the top, and now we're displaying webpages served locally we can be certain that we have a HUD working.

It's time to delete it.

Open the WBP_Unreveal widget, find the Text item in the Hierarchy panel. Now click on it to select it, and press Delete to get rid of it.

Compile, Save, press Play, and now we'll only have the HTML content displayed.

Installing Reveal.JS

As we have local content served we can now look at getting the reveal.js presentation system installed.

To keep things simple I'm going to create a reveal.js folder inside my UI directory which is a direct copy of the reveal.js Github repo. If you're working along with this guide you can now download the latest release from there, unzip it, and copy it into your UI folder

If you're using the version from the Unreveal Github repo the latest version may have been downloaded automatically as a Git Submodule, if not your Git client should either have an option to update submodules or (if you're using the command line) you can do it yourself using git submodule update --init --recursive in the project directory.

Our First Presentation

It's now time for us to start our first presentation!

Although first we have a problem- reveal.js sets a background color for the page, and we need it transparent. To fix this we create our own CSS file in "UI/unreveal.css" and just add a rule to make things transparent. The '!important' tells the browser that this will take priority over the rules it finds elsewhere, giving us the transparent background we want.

body {
  background-color: transparent !important;
}

Now we can replace "UI/index.html" with a simple reveal.js presentation.

Again I'm not writing a HTML course here and so in brierf o produce this I

Again either copy/paste from below or use the raw version here.

<!doctype html>
<htm>

	<head>
		<title>Simple Reveal.js demo</title>
		<meta name="description" content="A framework for easily creating beautiful presentations using HTML">
		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
		<link rel="stylesheet" href="./reveal.js/css/reveal.css">
		<link rel="stylesheet" href="./reveal.js/css/theme/black.css" id="theme">
		<!-- Theme used for syntax highlighting of code -->
		<link rel="stylesheet" href="./reveal.js/lib/css/zenburn.css">
    <!-- Overwrite the styles with some Unreveal stuff -->
    <link rel="stylesheet" href="./unreveal.css">
	</head>

	<body>
		<div class="reveal">
			<!-- Any section element inside of this container is displayed as a slide -->
			<div class="slides">
				<section>
					<h1>Unreveal</h1>
					<p>A presentation system with the power of a game engine</p>
				</section>

				<section>
					<h2>Built On</h2>
          <h3>Unreal Engine 4</h3>
          <p>The game engine by Epic Games</p>
          <h3>reveal.js</h3>
          <p>Hakim El Hattab's JavaScript presentation system</p>
          <h3>Blui</h3>
          <p>Web browser for the Unreal Engine, by Aaron Shea</p>
				</section>

				<section data-transition="slide" data-background="#4d7e65" data-background-transition="zoom"
					<h1>..and all done!</h1>
        </section>
			</div>
		</div>

		<script src="./reveal.js/lib/js/head.min.js"></script>
		<script src="./reveal.js/js/reveal.js"></script>
		<script>
			// More info https://github.com/hakimel/reveal.js#configuration
			Reveal.initialize({
				controls: false,  //Don't display controls
				progress: false, // Don't display  progress
				history: false, // Don't use browser history
				center: true, // Yes, centre the output
        overview: false, // Don't have a slide overview

				transition: 'slide', // none/fade/slide/convex/concave/zoom

				// More info https://github.com/hakimel/reveal.js#dependencies
				dependencies: [
					{ src: './reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
					{ src: './reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
					{ src: './reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
					{ src: './reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
					{ src: './reveal.js/plugin/zoom-js/zoom.js', async: true },
					{ src: './reveal.js/plugin/notes/notes.js', async: true }
				]
			});
		</script>
	</body>
</html>

When we press Play now we get the first page of our presentation displayed, reveal.js is plugged in and working. When we try to use the cursor keys though we see that we can't change page. This is a bit of a problem for a presentation and something we should really fix.

Unreveal running in editor

Hooking Input Keys

In order to control reveal.js we need to direct input through from UE4 back into the BluEye system, and to keep things simple we're just going to use the four cursor keys as that's plenty to control a presentation.

This will mean we could be flying about our UE4 game level with standard WASD keys, shooting monsters with the mouse, and at the same time controlling an overlaid presentation with the cursor keys. This is a good place to be.

To begin with we need to declare the input bindings in UE4. From the "Edit" menu choose "Project Settingsā€¦", and then select "Input" from the left tab.

We're looking at creating new Action Mappings, which are inputs similar to button presses where the event either happens or it doesn't. Axis mappings, which also have a value associated with them, are used for things such as joystick and mouse positions- but we don't need to bother about those.

Press the + button next to Action Mappings to create our first mapping, and name it RevealKeyDown. This is the name of the input events we'll receive.

Press the + next to the name once to create a single binding and from the dropdown choose "Down". We now have it set up so that pressing the "Down" key will fire off a RevealKeyDown event in any Blueprint set up to listen to input.

Repeat this to create the other three cursor bindings. RevealKeyUp with a binding of Up, RevealKeyLeft with a binding of Left, and RevealKeyRight with a binding of Right.

Binding cursor input

Bindings finished we can go back into BP_UnrevealHUD to set up our input bindings.

The first thing we do here is tell UE4 that our HUD needs to be informed of input events. This is done via an "Enable Input" event so we create one of those. For some reason here I had problems as it didn't allow me to choose one while the "Context Sensitive" option was checked on the node creation dialog, but once that was toggled off everything was fine.

This node needs to know which player it's taking controls for and so we also create a "Get Owning Player Controller" to get the player who owns the HUD, and plug this into the "Player Controller" of "Enable Input".

Enable input in HUD

Moving to a clear space on the Event Graph we can start to write our input handlers.

There are going to be four event nodes firing here, one each of InputAction RevealKeyRight, InputAction RevealKeyLeft, InputAction RevealKeyUp and InputAction RevealKeyRight.

Each one of these will be using our BluEye variable so we create a "Get" node alongside each of these inputs, and there's a "Special Key Press" node we can create off this which can send BLUI keypresses for keys such as cursor keys.

This would be great but there's actually a bug in BLUI which bites us now- and which I'm about to log an error report for. It has a list of the special keys, but this list isn't right- it gets the cursor keys in the wrong order.

This means that while we can use this to send the cursor keys we need to scramble them in order to work round the bug. InputAction RevealKeyRight needs to send "Down Arrow", InputAction RevealKeyLeft sends "Left Arrow", InputAction RevealKeyUp sends "Right Arrow", and InputAction RevealKeyDown sends "Up Arrow".

Hopefully this bug will be fixed in BLUI soon and InputAction RevealKeyRight can send "Right Arrow" and so forth, but for now this workaround at least means we can get things working.

Mixed up input wiring

Now if we go back and press Play we can use the right and left arrow keys to step through the presentation, while also using WASD to move ourselves round the level.

Callback into UE4

Setting Up Secondary Cameras

Fading Secondary Cameras

All Done