Saturday, September 26, 2015

How to disconnect SmartFox in Unity 5.1.3

Edit - The amazing people at SmartFoxServer let me try a pre-release of client version 1.6.2 which resolves this hang. Keep reading if you want to get some info on how to have a splash screen when your game is closing. You can read the full forum dialog here:
 http://smartfoxserver.com/forums/viewtopic.php?f=20&t=18107

Dear Unity Developers,

I recently updated to Unity5.1.3 and noticed a bad hang on a Windows Standalone Player build when closing my game.

I think that the Disconnect event can no longer be processed in OnApplicationQuit. I have a small workaround. I've reworked my project to load a new scene when the app is closed. I noticed that when my game got disconnected in a normal way (not on quit), it did not hang when I closed it. I researched what could be done, and I found this example in the Unity documentation.

http://docs.unity3d.com/ScriptReference/Application.CancelQuit.html

I'm including my entire SmartFoxManager.cs class. Hopefully this helps people make better use of SmartFox with Unity.

Essentially, what I do now is call Disconnect and then load a closing splash screen that waits for the disconnection (or times out) and calls Application.Quit().
using UnityEngine;
using Sfs2X;
using Sfs2X.Requests;
using Sfs2X.Entities;
using System.Collections;
using Sfs2X.Core;

// Created by Andy Martin

// Statics for holding the connection to the SFS server end
// Can then be queried from the entire game to get the connection

public class SmartFoxManager : MonoBehaviour
{
    public delegate void ConnectionLostDelegate(BaseEvent evt);

    public ConnectionLostDelegate ConnectionLost;

    private static SmartFox smartFox;
    private static SmartFoxManager mInstance;
    public static SmartFoxManager Instance
    {
        get
        {
            if(mInstance == null)
            {
                mInstance = new GameObject("SmartFoxManager").
    AddComponent(typeof(SmartFoxManager)) as SmartFoxManager;
            }

            return mInstance;
        }
    }

    //I have a different class set this connection
    public SmartFox Connection
    {
        get { return smartFox; }
        set 
        { 
            smartFox = value;
            smartFox.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
        }
    }

    private void OnConnectionLost(BaseEvent evt)
    {
        Debug.Log("OnConnectionLost SFSManager");

        if(disconnecting == true)
        {
            disconnecting = false;
            return;
        }

        //Call the event listeners
        if (ConnectionLost != null)
        {
            ConnectionLost(evt);
        }
    }

    public static bool IsInitialized
    {
        get
        {
            return (smartFox != null);
        }
    }

    void Awake()
    {
        DontDestroyOnLoad(this);
    }

    //FixedUpdate is called once per 20 ms
    void Update()
    {
        if (smartFox != null)
        {
            smartFox.ProcessEvents();
        }
    }

    public bool disconnecting = false;
    private bool allowQuitting = false;

    // Handle disconnection automagically
    // ** Important for Windows users - can cause crashes otherwise
    void OnApplicationQuit()
    {
        if (disconnecting == true)
        {
            return;
        }

        Debug.Log("SmartFoxManager OnApplicationQuit");

        if (smartFox != null && smartFox.IsConnected)
        {
            disconnecting = true;

            smartFox.Disconnect();
            StartCoroutine("DelayedQuit");

            if (allowQuitting == false)
            {
                Debug.Log("Cancelled Quit");
                Application.CancelQuit();
            }
        }
        else
        {
            //Quit, you are not connected
            return;
        }

        Debug.Log("End of SmartFoxManager OnAppQuit");
    }

    //Shows an ending splash
    IEnumerator DelayedQuit()
    {
        Debug.Log("Delayed Quit");
        //Take the game out of full-screen mode
        Screen.fullScreen = false;
        Application.LoadLevel("EndSplash");

        int maxTries = 3;
        int tries = 0;
        while (disconnecting == true && ++tries <= maxTries)
        {
            Debug.Log("Hanlding Disconnect");
            yield return new WaitForSeconds(1.0f);
        }

        allowQuitting = true;
        Debug.Log("Allowing Quitting");
        Application.Quit();
    }
}

Wednesday, April 8, 2015

Why not to use Baked GI in Unity3D

Originally, we knew nothing about lighting. We controlled all scene lighting with ambient light. Later I read about Unity's deferred lighting path. http://docs.unity3d.com/Manual/RenderTech-DeferredLighting.html  Because we have several rotating spotlights that need to shine in real time, the cost of each light and what it hits is linear with deferred lighting. That is legacy lighting now because Unity has replaced deferred lighting with deferred shading. http://docs.unity3d.com/Manual/RenderTech-DeferredShading.html

For a while, we tried to make Baked Lightmaps. These were supposed to give us some lighting for "free" at the cost of having to bake them a bit beforehand. We set up light probes around the scene for animating characters and objects. Firstly, those baked lightmaps took around 100mb of storage, which we were storing with git. That got quite bloated and made our git repo start to push it's 1 GB (GitHub imposed limit on a repository) limit. When we upgraded to Unity 5, we noticed several dumb things we were doing. For instance, we had two directional lights in opposing directions. Directional lights have no starting position, they just start from the outermost edge of your scene boundaries and extend to the other. This was how we did it for about 2 years because we were focused on coding up a better experience rather than the visual appearance of the room. We wanted to take advantage of the new Global Illumination, so we needed to use real indoor lighting instead of directional lights.

Once we upgraded to Unity 5, we noticed that the majority of our lighting was not taking advantage of the Global Illumination. We did a complete revamp of our lighting. Firstly, we removed our directional lights and replaced it with a chandelier that had one point light and an emissive material. When we went to use our Baked GI, there were horrible splotches. I tried increasing the resolution, changing the materials, turning on Final Gather. Usually one thing would fix and another would break. I decided to turn of Baked GI and go only with precomputed realtime (essentially it bakes the paths the light will travel, but that is all).  Because of deferred lighting, we haven't noticed any issues, and now we can bake the precomputed light path in only a few seconds

We're going to need to see how it works on the crappiest laptops, but we won't be going back to Baked GI. Look forward to seeing you in Club Consortya (Preview) releasing summer 2015.