Jump to content
  • Advertisement

Making a website from your game, for your game

Demosthenes

1078 views

Hello there.

As mentioned in a previous blog entry, we're developing an adventure game called Outsider, where every puzzle is different. Outsider is being developed in Unity using a plugin called SVG importer, meaning most graphics in the game are vectorial.

In the past year, we have improved the graphics a lot:

January 2018

labbefore.png.6aea4d06eae78229c9bb9f4601b3b372.png

 

 

 

 

 

 

 

 

 

 

April 2019

labafter.thumb.png.c1c9669c584d18522f42168c34ddac33.png

 

Details were added to the graphics, the lighting was tuned and we also started using Unity's recent post-processing profiles. Meanwhile, the website we built for the game still used screenshots from the old version and looked ancient:

oldwebsite.thumb.png.9d7c8e07b3f603b6098a1dc4f2c101b3.png

 

 

 

 

 

 

 

 

 

The website was also minimal, consisting of just images and gifs from the game interspersed with minimal text. So, since all the recent browsers support SVG, we decided to use the graphics from this scene, directly from Unity, to create a new website:

newwebsite.thumb.png.354c3b814fb52ad0bd2a8caed910d30d.png

If you want to take a look, it's live at www.onceabird.com.

So, on to the code. Unity supports extending the GUI of your behaviours, so we inherited the Editor class in a WebsiteGeneratorEditor class to create a single button to export the scene layout:

[CustomEditor(typeof(WebsiteGeneratorScript))]
public class WebsiteGeneratorEditor: Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        serializedObject.Update();

        WebsiteGeneratorScript websiteGeneratorScript = (WebsiteGeneratorScript)target;
        if(GUILayout.Button("Export Scene"))
        {
            websiteGeneratorScript.ExportScene();
        }
    }
}

Each Editor class references another script as a target, in our case it's named WebsiteGeneratorScript. Here's the full source code: WebsiteGeneratorScript.cs, I'll go over it bit by bit. But first some context.

The SVG plugin we're using creates an SVG Renderer component (as opposed to the normal Sprite Renderer or other Renderer components) on each behaviour that uses an SVG asset:

SVGRenderer.png.8f8ace5ad61a32c595f0061e0cbfadbb.png

 

 

 

 

 

 

 

 

 

Although some code here is specific to SVG Renderer, most of it is based on Unity's standard position, rotation and scale Transform, so it should be easy to adapt to your project.

The exported scene's SVG Renderers are contained in a single root transform named Room:

RoomStructure.thumb.png.a0e0201a13dbacacf814ac849d1a1b4f.png

And here's how the WebsiteGeneratorScript component is configured in this scene:

WebsiteGeneratorScript.thumb.png.a61facec34fdbe7049257622da6145bc.png

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

We'll gloss over the details for now. The WebsiteGeneratorScript class contains a recursive method named ExportTransform:

string transformText = "";
        
ExportTransform(GameObject.Find("Room").transform, ref transformText);

The method searches for an SVGRenderer on each transform, starting from the Room root transform. The transformText variable contains the generated HTML text that will be inserted into an HTML file in the end. Not all the elements in the scene will be exported, so the first thing the method does is return if the transform is in the excludedTransforms list (the contents of which you can see in the WebsiteGeneratorScript component image above):

    private void ExportTransform(Transform transform, ref string transformText)
    {
        if(Array.IndexOf<Transform>(excludedTransforms, transform) >= 0)
        {
            return;
        }

If an SVGRenderer component is found, it generates the corresponding HTML img tag:

SVGRenderer renderer = transform.GetComponent<SVGRenderer>();

if(renderer != null && !transform.name.ToLower().Contains("interactive"))
{
	float width = renderer.vectorGraphics.bounds.size.x * scale * transform.localScale.x;
  	float height = renderer.vectorGraphics.bounds.size.y * scale * transform.localScale.y;

  	float left = transform.position.x * scale - width / 2.0f - (renderer.vectorGraphics.pivotPoint.x - 0.5f) * width;
  	float top = -transform.position.y * scale - height / 2.0f - (renderer.vectorGraphics.pivotPoint.y - 0.5f) * height;

  	string[] pathParts = UnityEditor.AssetDatabase.GetAssetPath(renderer.vectorGraphics).Split(new char[] { '/' });

  	string path = "";
  	for(int i = 3; i < pathParts.Length - 1; i++)
	{
		path += pathParts[i] + "/";
	}
  	
  	string fileName = pathParts[pathParts.Length - 1];

  	path += fileName.Substring(0, fileName.IndexOf(".")) + ".svg";

  	if(positionDifferencesDic.ContainsKey(path))
	{
		Vector2 diff = positionDifferencesDic[path];
		left += diff.x;
		top += diff.y;
	}

	string rotationText = transform.eulerAngles.z == 0.0f ? "" :
           "transform: rotate(" + (-transform.eulerAngles.z) + "deg);" +
           "transform-origin: " + (renderer.vectorGraphics.pivotPoint.x * 100) + "% " + (renderer.vectorGraphics.pivotPoint.y * 100) + "%;";

	string className;
  	if(cssClassesDic.ContainsKey(path))
    	{
		className = " " + cssClassesDic[path];
	}
	else
	{
		className = "";
	}

  	transformText += "<img src='" + path + "' " + 
                    "class='transform-image" + className + "' " +
                    "style='left: " + left +
                    "%; top: " + top + "%; width: " +
                    width + "%; height: " + height + "%; " + 
                    "z-index: " + renderer.sortingOrder + "; " +
                    rotationText +
                    "' />\n";
}

Since we wanted the website to be responsive, the left, top, width and height style properties are calculated in percentage, and every HTML img element uses position absolute.

Width and height are calculated first, using a scale variable that is applied to all exported SVG, determining the global exported website scale, the transform's local scale and the size of the SVG itself. If you're using sprites, the width and height of the base image should be used here.

Next left and top are calculated. The scale variable is also used, as is the SVG Renderer's vectorGraphics.pivotPoint, which is the center point of any rotation.

The path variable contains the URL to the SVG asset, which must be placed separately from this code on your website's file structure. The UnityEditor.AssetDatabase is used to find the file path on the Unity project and replicate it to the exported website path.

An example exported HTML img tag looks like this:

<img src='SVG/CeilingLamp.svg' 
     id="ceiling-lamp-1" 
     class='transform-image ceiling-lamp' 
     style='left: -24.65349%; top: -58.18391%; width: 10.21898%; height: 38.1918%; z-index: 50;' />

Unity's sortingOrder corresponds to CSS's z-index.

Here's the transform-image CSS class:

.transform-image
{
	position: absolute;
	overflow: visible;
	padding: 0px;
	margin: 0px;
	border: 0px;
	transform: translate3d(0,0,0);
	max-height: 100%;
}

(The rest of the source code for the webpage can be viewed on our website, none of the CSS or JS is minified or obscured.)

The generated HTML was just the starting point: 

  • We had to condense several SVGs into a single file to improve performance, since SVG rendering still has a way to go on most browsers.
  • Some graphics were created just for the website, such as the social media links on the bottom right corner and the trophies.
  • We used CSS animations and transitions to do zooms but these made the SVG blurry in the iOS version of Safari, so we had to upscale the "clickable" elements in relation to the rest of the elements on the page.
  • The shaking effect was also done with CSS animations.
  • To simulate lighting on the webpage we created a transparent SVG shaped like the lamp light and used blend modes to create the simulated effect.

 

If you want to know more about Outsider and/or our development process, please follow us on Twitter or Instagram ( or on www.onceabird.com ).


And if there's something unclear or missing in this article please let us know. Thanks for reading!



0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!