Maxime FRAPPAT

Hum …no thanks ! – Lordinaire

Tag: Editor

[Unity] Custom build for WP8/W8

If your game target the Windows Phone 8 or Windows Store platform, you probably want to use some specific feature like the share, live tiles, …

This can be done by using some tricks that I already explain in a previous thread (http://blog.lordinaire.fr/2014/09/unity-interact-between-unity-and-windows-phone-8-windows-store-apps/) but I might be pretty cool to build automatically custom files like splashscreen, MainPage or tiles.

Create a specific menu item

The first step is to create a menu item to easily access to our build.

using System.Linq;
using System.Security.Policy;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;

public class AutoBuilderMenu : MonoBehaviour
{
	[MenuItem("AutoBuilder/WP8")]
	private static void PerformBuild()
	{
		// TODO : Build and replace files
	}
}

For more information about MenuItem see http://blog.lordinaire.fr/2014/10/unity-create-custom-menu-items/

Simple build

using System.Linq;
using System.Security.Policy;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;

public class AutoBuilderMenu : MonoBehaviour
{
	[MenuItem("AutoBuilder/WP8")]
	private static void PerformBuild()
	{
		// Save current editor build target
		var currentTarget = EditorUserBuildSettings.activeBuildTarget;
		EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.WP8Player);

		var buildPath = GetBuildPath(target);
		var path = Path.GetDirectoryName(buildPath);

		var folderExists = Directory.Exists(path);
		if (!folderExists)
			Directory.CreateDirectory(path);

		// Create solution folder and project files
		BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, target, BuildOptions.None);

		// Set saved build target
		EditorUserBuildSettings.SwitchActiveBuildTarget(currentTarget);
	}

	private static string GetProjectName()
	{
		string[] s = Application.dataPath.Split('/');
		return s[s.Length - 2];
	}

	private static string[] GetScenePaths()
	{
		return EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray();
	}

	private static string GetBuildPath(BuildTarget target)
	{
		var extension = String.Empty;

		return String.Format("../Builds/{0}/{1}/{2}/{3}",
			GetProjectName(),
			PlayerSettings.bundleVersion,
			target.ToString(),
			PlayerSettings.bundleIdentifier);
	}
}

The project will be create in the folder ../Builds/{ProjectName}/{BundleVersion}/{TargetPlatform}/{BundleIdentifier} so we have a build for each version of our game in a specific folder.

Custom build

using System.Linq;
using System.Security.Policy;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;

public class AutoBuilderMenu : MonoBehaviour
{
	[MenuItem("AutoBuilder/WP8")]
	private static void PerformBuild()
	{
		// Save current editor build target
		var currentTarget = EditorUserBuildSettings.activeBuildTarget;
		EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.WP8Player);

		var buildPath = GetBuildPath(target);
		var path = Path.GetDirectoryName(buildPath);

		var folderExists = Directory.Exists(path);
		if (!folderExists)
			Directory.CreateDirectory(path);

		// First build to create solution folder and project files
		BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, target, BuildOptions.None);

		// Replace custom files
		UpdateWP8Settings(buildPath);

		// Rebuild with replacing files
		BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, target, BuildOptions.None);

		// Set saved editor build target
		EditorUserBuildSettings.SwitchActiveBuildTarget(currentTarget);
	}

	private static void UpdateWP8Settings(string buildPath)
	{
		// SplashScreenImage
		var replace = string.Concat(Application.dataPath, "/../BuildSettings/WP8/SplashScreenImage.jpg");
		var current = string.Format("{0}/{1}/SplashScreenImage.jpg", buildPath, PlayerSettings.bundleIdentifier);
		if (File.Exists(replace))
			File.Copy(replace, current, true);

		// MainPage.xaml.cs
		replace = string.Concat(Application.dataPath, "/../BuildSettings/WP8/MainPage.xaml.cs");
		current = string.Format("{0}/{1}/MainPage.xaml.cs", buildPath, PlayerSettings.bundleIdentifier);
		if (File.Exists(replace))
			File.Copy(replace, current, true);		

		// ApplicationIcon
		replace = string.Concat(Application.dataPath, "/../BuildSettings/WP8/ApplicationIcon.png");
		current = string.Format("{0}/{1}/Assets/ApplicationIcon.png", buildPath, PlayerSettings.bundleIdentifier);
		if (File.Exists(replace))
			File.Copy(replace, current, true);

		// WMAppManifest
		replace = string.Concat(Application.dataPath, "/../BuildSettings/WP8/WMAppManifest.xml");
		current = string.Format("{0}/{1}/Properties/WMAppManifest.xml", buildPath, PlayerSettings.bundleIdentifier);
		if (File.Exists(replace))
			File.Copy(replace, current, true);
	}

	private static string GetProjectName()
	{
		string[] s = Application.dataPath.Split('/');
		return s[s.Length - 2];
	}

	private static string[] GetScenePaths()
	{
		return EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray();
	}

	private static string GetBuildPath(BuildTarget target)
	{
		return String.Format("../Builds/{0}/{1}/{2}/{3}",
			GetProjectName(),
			PlayerSettings.bundleVersion,
			target.ToString(),
			PlayerSettings.bundleIdentifier);
	}
}

The trick is to build twice the game. First we build to create all files then we replace our custom files and we rebuild to use these new files.

A more elegant way

private static void PerformBuild(BuildTarget target, bool isBeta = false)
{
	var currentTarget = EditorUserBuildSettings.activeBuildTarget;
	EditorUserBuildSettings.SwitchActiveBuildTarget(target);

	var buildPath = GetBuildPath(target);
	var path = Path.GetDirectoryName(buildPath);

	var folderExists = Directory.Exists(path);
	if (!folderExists)
	{
		Directory.CreateDirectory(path);
	}

	BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, target, BuildOptions.None);

	if (target == BuildTarget.WP8Player)
	{
		UpdateWP8Settings(buildPath, string.Concat(Application.dataPath, "/../BuildSettings/WP8/"), string.Concat(buildPath, "/"));
		if (isBeta)
		{
			UpdateWP8Settings(buildPath, string.Concat(Application.dataPath, "/../BuildSettings/WP8_Beta/"), string.Concat(buildPath, "/"));

		}
		BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, target, BuildOptions.None);
	}

	EditorUserBuildSettings.SwitchActiveBuildTarget(currentTarget);
}

private static void UpdateWP8Settings(string buildPath, string sourceRootPath, string destinationRootPath = "")
{
	var files = Directory.GetFiles(sourceRootPath);
	foreach (var file in files)
	{
		var replace = file;
		var fileName = replace.Split('/').LastOrDefault().Split('\\').LastOrDefault();
		var current = string.Concat(destinationRootPath, "/" + fileName);
		File.Copy(replace, current, true);
	}

	var directories = Directory.GetDirectories(sourceRootPath);
	foreach (var directory in directories)
	{
		var folderName = directory.Split('/').LastOrDefault().Split('\\').LastOrDefault();
		UpdateWP8Settings(buildPath, directory, destinationRootPath + "/" + folderName);
	}
}

You just need to add all your files in the folder BuildSettings/WP8 and/or BuildSettings/WP8_Beta with the same hierarchy as the project and it’s done ! Thanks Jonathan for the tips ;)

unity_custom_build

It’s the same way with Windows Store apps :)

[Unity] Create custom menu items

The Unity editor is very powerful and allows a huge extensibility. In this post, I will focus on how create a custom Menu Item.

The MenuItem attribute allows you to add menu items to the main menu and inspector context menus.

Simple Menu Item

using UnityEditor;
using UnityEngine;

public class MyCustomMenu : MonoBehaviour
{
	[MenuItem("MyMenu/Hello world")]
	private static void SayHello()
	{
		Debug.Log("Hello world!");
	}
}

MenuItem(string itemName);

Menu Item with shortcut

using UnityEditor;
using UnityEngine;

public class MyCustomMenu : MonoBehaviour
{
	[MenuItem("MyMenu/Hello world %h")]
	private static void SayHelloWithShortcut()
	{
		Debug.Log("Hello world!");
	}
}

MenuItem(string itemName);

The shortcut is set by adding %h. For more information see http://docs.unity3d.com/ScriptReference/MenuItem.html

Menu Item with validation

using UnityEditor;
using UnityEngine;

public class MyCustomMenu : MonoBehaviour
{
	[MenuItem("MyMenu/Hello world")]
	private static void SayHello()
	{
		Debug.Log("Hello world!");
	}

	[MenuItem("MyMenu/Hello world", true)]
	private static bool CanSayHello()
	{
		return Selection.activeGameObject != null;
	}
}

MenuItem(string itemName, bool isValidateFunction);

The second parameter of the MenuItem constructor allow you to add a validation method who will be launched before the main method.

Menu Item with priority

using UnityEditor;
using UnityEngine;

public class MyCustomMenu : MonoBehaviour
{
	[MenuItem("MyMenu/Hello world", false, 1)]
	private static void SayHello()
	{
		Debug.Log("Hello world!");
	}

	[MenuItem("MyMenu/ByeBye", false, 3)]
	private static void SayBye()
	{
		Debug.Log("Bye bye!");
	}
}

 MenuItem(string itemName, bool isValidateFunction, int priority);

The third parameter of the MenuItem constructor allow you to add a priority level to be able to order items in the menu.

Powered by WordPress & Theme by Anders Norén

%d bloggers like this: