20 September 2014

Bugsnag integration for Unity

Recently we started using bugsnag in kweetet. Bugsnag is a website that exposes an api that you can use to send your error reporting to, and then you have a nice interface to follow up on these errors, assign them to users, resolve them, etc. All well described here. What a lifesaver it has been to us!

They also have a unity notifier. Too bad it only works on Android and iOS. Since we are developing for the unity webplayer I had no choice but to build my own notifier. This turns out to be really easy, starting with the notifier documentation. There's one subtlety that is not mentioned in the docs: you must provide a stack trace of at least one line, or the error report won't show. I received tremendous good help from the people on their support chat.

The one problem with the unity webplayer is that it runs in a security sandbox. With the WWW class you can only access websites that are on the same domain as the webplayer. So sending reports from kweetet.be to bugsnag.com is a no-go.

With a bit of research I managed to set up a proxy on that forwards the error reports to bugsnag, and with that solution I was able to receive the errors, hurray! Kweetet runs on nginx, I added this to our server configuration (something similar is possible with apache):

location ~ /bugsnag {
 rewrite /bugsnag / break;
 proxy_set_header X-Forwarded-Host $host;
 proxy_set_header X-Forwarded-Server $host;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_pass https://notify.bugsnag.com;
}

This enabled me to send the reports to our own domain which then gets forwarded. Now we receive all errors that occur in the game from anyone! The setup is similar to the official unity notifier. For web players you can specify another reporting url, depending on the proxy that you've set up. For all other platforms it should work with the default url.

I started a bitbucket project, I'll be improving this notifier since I keep on improving it for kweetet. This is the link: https://bitbucket.org/grrava/unitybugsnag.

It should also work on android and ios. I confirmed that it worked on android but not on ios. On android however I wasn't able to get a stacktrace, so probably the official unity notifier from bugsnag will be more suited for those two platforms.

As always, use the code at will - let me know if it helped you in any way or if you have questions, post them here or on the bitbucket project page.

08 August 2014

Combine meshes in Unity

If there are a lot of objects in a scene that share the same material but do not share the same meshes you can lower the number of draw calls by combining them into one single mesh.

Unity has a "MeshCombineUtility" in the standard assets that is supposed to do exactly that, but several of our combined meshes were missing parts in an unpredictable way. It uses a hashtable to index the used materials. I replaced it with a typed dictionary and that fixed it. Somehow we must have had hash collisions.

In the documentation of Mesh.Combinemeshes there is a script that intends to do the same, with simpler code. Oddly, that script does combine multiple meshes into one but it generates an object without materials.

So I wrote my own version of the CombineChildren script that combines both into one, working script:

using UnityEngine;
using System.Collections.Generic;

[AddComponentMenu("Mesh/Combine Children")]
public class CombineChildren : MonoBehaviour {

void Start()
{
 Matrix4x4 myTransform = transform.worldToLocalMatrix;
 Dictionary<string, List<CombineInstance>> combines = new Dictionary<string, List<CombineInstance>>();
 Dictionary<string , Material> namedMaterials = new Dictionary<string, Material>();
 MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
 foreach (var meshRenderer in meshRenderers)
 {
  foreach (var material in meshRenderer.sharedMaterials)
    if (material != null && !combines.ContainsKey(material.name)) {
   combines.Add(material.name, new List<CombineInstance>());
   namedMaterials.Add(material.name, material);
    }
 }

 MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
 foreach(var filter in meshFilters)
 {
  if (filter.sharedMesh == null)
   continue;
  var filterRenderer = filter.GetComponent<Renderer>();
  if (filterRenderer.sharedMaterial == null)
   continue;
  if (filterRenderer.sharedMaterials.Length > 1)
   continue;
  CombineInstance ci = new CombineInstance
  {
   mesh = filter.sharedMesh,
   transform = myTransform*filter.transform.localToWorldMatrix
  };
  combines[filterRenderer.sharedMaterial.name].Add(ci);

  Destroy(filterRenderer);
 }

 foreach (Material m in namedMaterials.Values)
 {
  var go = new GameObject("Combined mesh");
  go.transform.parent = transform;
  go.transform.localPosition = Vector3.zero;
  go.transform.localRotation = Quaternion.identity;
  go.transform.localScale = Vector3.one;

  var filter = go.AddComponent<MeshFilter>();
  filter.mesh.CombineMeshes(combines[m.name].ToArray(), true, true);

  var arenderer = go.AddComponent<MeshRenderer>();
  arenderer.material = m;
 }
}}

04 August 2014

Quickly adding bloom to Unity

Recently I wanted to add bloom to a personal project I'm working on. In the Image Effects (Pro Only) package that you can import directly via the editor you have exactly what I wanted, a simple bloom effect. Attach it to the camera and boom you have bloom.

Sad thing though is that the compiler spits out a bunch of warnings when you import that package, clearly it's been a while since the package has seen some love from the Unity developers. I can understand that their priorities lie elsewhere.
Obviously I code in C# and I wanted to add the bloom effect on the main camera and change its public values via code. Since the effect was written in unityscript this was not possible.

So I spent some time converting it to C#, you can download it here if you want to use it. I did not clean the code or anything, just made sure it worked like it did before and that the compiler was completely happy with it.

22 April 2014

Render the view frustum of a camera in Unity

When you select a camera in the Unity editor, gray lines indicate where the view frustum is. When it's deselected the lines are obviously not rendered. Sometimes however, we do want to see these lines even if the camera is not selected, for example when implementing custom culling code as I am doing at the moment. For that I found a nice piece of code on the Unity forums. I put it into a monobehaviour: attach the following to a camera and the frustum will be rendered in the editor.

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class FrustumViewer : MonoBehaviour
{
 void Update()
 {
  DrawFrustum(this.camera);
 }

 void DrawFrustum ( Camera cam ) {
  Vector3[] nearCorners = new Vector3[4]; //Approx'd nearplane corners
  Vector3[] farCorners = new Vector3[4]; //Approx'd farplane corners
  Plane[] camPlanes = GeometryUtility.CalculateFrustumPlanes( cam ); //get planes from matrix
  
  Plane temp = camPlanes[1]; camPlanes[1] = camPlanes[2]; camPlanes[2] = temp; //swap [1] and [2] so the order is better for the loop
  for ( int i = 0; i < 4; i++ ) {
   nearCorners[i] = Plane3Intersect( camPlanes[4], camPlanes[i], camPlanes[( i + 1 ) % 4] ); //near corners on the created projection matrix
   farCorners[i] = Plane3Intersect( camPlanes[5], camPlanes[i], camPlanes[( i + 1 ) % 4] ); //far corners on the created projection matrix
  }
  
  for ( int i = 0; i < 4; i++ ) {
   Debug.DrawLine( nearCorners[i], nearCorners[( i + 1 ) % 4], Color.red, Time.deltaTime, true ); //near corners on the created projection matrix
   Debug.DrawLine( farCorners[i], farCorners[( i + 1 ) % 4], Color.blue, Time.deltaTime, true ); //far corners on the created projection matrix
   Debug.DrawLine( nearCorners[i], farCorners[i], Color.green, Time.deltaTime, true ); //sides of the created projection matrix
  }
 }
 
 
 
 Vector3 Plane3Intersect ( Plane p1, Plane p2, Plane p3 ) { //get the intersection point of 3 planes
  return ( ( -p1.distance * Vector3.Cross( p2.normal, p3.normal ) ) +
          ( -p2.distance * Vector3.Cross( p3.normal, p1.normal ) ) +
          ( -p3.distance * Vector3.Cross( p1.normal, p2.normal ) ) ) /
   ( Vector3.Dot( p1.normal, Vector3.Cross( p2.normal, p3.normal ) ) );
 }
}

31 March 2014

Recursive ffmpeg on centos

This is just about a small tidbit I had to write. It took me a while to get it completely right (since I'm no linux guru) so I'll post about it here for future reference.

For kweetet we use several mp3's in our exercises: all our assignments are both in text and audio, some of the questions are audio files and all the feedback on the answers is again with text and audio. We noticed that downloading the exercises on mobile devices could take a while, partly because of the audio. When I took a closer look, we noticed that files where too large: 320kbps for voice while 96kbps would suffice. These mp3's were everywhere in a complex folder structure, so I had to write a script on our server hosting the authoring tool to convert all these mp3s to a lower bit rate at export.

So, to be able to convert mp3s to a lower bit rate I needed to install ffmpeg on the centos machine. No magic there, centos' yum is all you need: just follow the steps of this post

And then the command to do the conversion recursively over all folders, it turned out I could do this in one go! The trick was: a/ discover that you can recurse over folders with find, b/ you can execute a command on every result, c/ you can execute multiple commands for every result, by just having multiple -exec parameters. Resulting in:

find ./export/ -name "*.mp3" -exec ffmpeg -i {} -codec:a libmp3lame -b:a 96k out.mp3 \; -exec mv out.mp3 {} \;