Instantiating Objects based on OData

Ok. Based on the previous posts, we've got the ground work covered. From a components point of view, we're nicely set up. Now, how can we get some nicely colored, glowing spheres actually representing business data?

First things first, let's get a construct going. A world where we can run around and stare at all the cool stuff we can do. We need:

  • A plane, so we won't fall into the neverending void
  • A first person, representing us with the ability to walk and look around
  • Some light to brighten our awesomeness
  • Preferably some reference points, so we can get our bearing

As this is all pretty much basic Unity, so I won't go too much into details about how to set this up. The internet is nicely stuffed with tutorials on this stuff, so let's focus on the less obvious parts. For now, the environment is basic enough:
World

Essentially, I just have a plane to walk around on, with a big sphere around it. The water basin serves as our reference point (nicely centralised). There's a light and a first person that can walk around. If you replace that first person with the one provided in the Oculus API, we can already put our HMDs on and start feeling dizzy:

WorldVR

Now, to get some content into it. This is going to require scripting, so let's dive straight in. In a script associated with the basic environment, we would centralise our interactions with the user. I did this by associating the script to the group of objects that constitute the plane, the sphere etc..

In that script, let's imbed an action on the user tapping the "h" key on the keyboard and based on it, start a coroutine to load the data:

// Instantiate based on external data 
if (Input.GetKeyDown ("h")) { 
    StartCoroutine ("LoadOData");
}

This will separate the loading of the data in a separate thread, implying the interaction process is not dependent on it.

The coroutine itself:

IEnumerator LoadOData() {
    //Load JSON data from a URL
    string url = "file://c:/test/Data";
    WWW BPdata = new WWW(url);
    yield return BPdata;
    if (BPdata.error == null)
    {
        //Sucessfully loaded the JSON string
        Debug.Log("Loaded following JSON string" + BPdata.text);
        //Process the data in JSON file
        ProcessData(BPdata.text);
    }
    else
    {
        Debug.Log("ERROR: " + BPdata.error);
    }
}

This loads the data from a file I've stored on the local drive for now (accessing data over the internet is comparable). Using the Yield statement actually separates the processing in yet another thread (which doesn't hurt). The data is retrieved or not. Either way, we push an entry in the Debug log just to see what's going on. If we successfully retrieve the file, we push the string into our processing routine.

The processing routine itself is a little trickier:

private void ProcessData(String JsonString)
{
    JsonData BpString= JsonMapper.ToObject(JsonString);
    Debug.Log (BpString["d"]["results"].Count);
    float minVolume = 0; 
    float maxVolume = 0; 
    for (int i = 0; i<BpString["d"]["results"].Count; i++) 
    { if ( minVolume == 0)
        { minVolume = float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());}
        if ( int.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString()) < minVolume )
        { minVolume = float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());}
        if ( int.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString()) > maxVolume )
        { maxVolume = float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());}
    }

    for(int i = 0; i<BpString["d"]["results"].Count; i++)
    {
        Vector3 pos = new Vector3(UnityEngine.Random.Range(-6.00F,6.00F),0.5f,UnityEngine.Random.Range(-6.00F,6.00F)); 
        GameObject ball = Instantiate(myInstBall, pos, Quaternion.identity) as GameObject;
        ball.name = "Ball" + i; 
        MyScript = ball.GetComponent<BallAttributes_C>();
        MyScript.partnerID = int.Parse(BpString["d"]["results"][i]["Partner"].ToString());
        MyScript.salesVolume = int.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());
        MyScript.salesVolumeC = (float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString())/ maxVolume );
        MyScript.risk = BpString["d"]["results"][i]["Risk"].ToString();
    } 
}

To start off, the routine imports the data string and converts it into a JSON object we can more easily work with. I'm using a nice library for this (LitJSON), which is referenced in a tutorial on how JSON can be consumed in Unity.

Having the JSON mapper object allows us to flexibly extract information from it, like the count of objects under the "results" node (BpString["d"]["results"].Count).

In this example, I'm representing business partners as floating balls, where the size of the object is based on a sales volume and the color is based on a risk indication. As the salesvolumes can vary in a large range I'm determining the highest and the lowest volume in the file and using those as the range to show the sales volume weighted.

For every entry in the file, I'm then instantiating an object which is defined as a prefab in unity and assigned as a variable to the script ("myInstBall"). The object is given a random xyz position based on our reference point (-6.00F,6.00F).

I'm also the giving it a specific name and I'm retrieving a script ("BallAttributes_C") associated with the prefab, which I'm then using to associate specific variables (ID, Salevolume, weighted salesvolume and risk) to it. This instantiates our objects.

The script ("BallAttributes_C") is leveraged to change the appearance of the objects, by influencing their color:

        switch (risk) {
        case "High": 
            gameObject.renderer.material.color = Color.red;
            break;
        case "Medium": 
            gameObject.renderer.material.color = Color.gray;
            break;
        case "Low":
            gameObject.renderer.material.color = Color.green;
            break;
        }

Or their size

        gameObject.transform.localScale = new Vector3( gameObject.transform.localScale.x, salesVolumeC, gameObject.transform.localScale.z); 

Which gives us this:
Graph

3D Object instantiation in a virtual room based on external JSON data from an OData service! Hurray!

Comments

comments powered by Disqus