Showing posts with label Unity3D. Show all posts
Showing posts with label Unity3D. Show all posts

Friday, August 12, 2011

Investigating UDK - First Impressions

Bold claim Unreal... But can you back it up?
UDK vs Unity3D

So I've been meaning to check out the Unreal Development Kit (UDK) and compare it to Unity3D. Both seem like viable options for an Indie game designer, with UDK making more profit margin for cheaper games (because of the lack of licensing costs at lower revenues). So I've installed the July 2011 Beta, and have started working a little with UnrealScript - its much like JavaScript/mono in Unity3D so this could be good.

Programming - First impressions would say that for a beginning programmer, unity3D is much easier to learn. Being able to code  in the smaller chunks (behaviors) and the availability (asset store, this blog, forums, others...) of free scripts to (steal) use fairly - that the idea of compiling and writing full classes for pawns and actors is a little more challenging. Point for Unity.

Tool Based Assets - However the reverse is true for aspiring level designers. The inbuilt-tools available in unreal for building levels and generating lightmaps are slightly superior to that of unity. Sure Unity has a passable terrain tool and scene lightmapping, but I have all kinds og trouble trying to get that to work right. Point for Unreal.

Outside Assets - Generating assets in outside programs, while not my forte' seems to be easier in Unity. The asset browser in Unreal is convoluted at first glace, but perhaps this is just because I'm so used to Unity. Importing assets from 3dsmax, photoshop and others is 'automatic' for Unity, and a little more of a process for Unreal. But because I haven't had enough time to fully investigate, I can't award a point in this class just yet.

Build Platforms - If you don't buy the extra pro versions of unity, you only get web player and pc/mac. Compare that to the currently available platforms for Unreal of Windows and iOS. iOS is huge in the current casual gaming market and adds a lot to the value of UDK. But if you have the change to buy a full Unity Suite (something like $2500) you'll unlock Android, iOS (forget about Xbox and Ps3 unless you are really serious). Points? Depends on who you are.

Rushed Conclusion after first impressions: For the first time game developer, go with Unity - the web-player is a easy way to get your game to the masses. For the serious developer looking to make money (without investment), my vote would be for UDK at this point. If you have the money, get Unity3D pro.

Another kind of "radar" for Unity3D

Icon Based radar showing a NPC  ("Steve") selected - an icon appears over his character


Introduction

Following on from my experimentation in the "RealRadar" class, I wanted to make something that

  • Didn't spawn a million prefabs
  • Allowed users to select what objects they were searching for
  • Display addtional info on mouseover
  • Show objects as Icons overlayed on the 3d world.
To improve performance, I wanted to create a database of objects that were "searchable" at design time (editor) and then let the user pick form the list of objects at run time. They would then be added to a list of "objects I'm searching for" which the script would find in the database, assign an icon, then display it on the screen using OnGUI(). To this end, I will be using a utlity class I shared in a previous post, the hashlist.

The IconRadar behavior serves two purposes. It displays a search button, displayed in the area indicated by searchingGuiButtonRect - which when clicked opens a window that is displayed in the area indicated by searchingGuiScreenRect which allows users to control what objects are in the searchingItems hashlist. The IconRadar script will need to have a guiskin attached that defines the style "Search" - assign whatever look you want to this style. "Search" will be used to define the button in the top left, the button that toggles the interface on and off.

The hashlists are objects that have the behavior ItemSearchable attached. The ItemSearchable behavior is easy to use - just place it on any item that you want to appear in the list, and ensure that the object has a unique name. The ItemSearchable behavior uses a method I cribbed from PHP - base64encode, to encode the items name into a GUID.

Firstly, the IconRadar C# Script
You only need one instance of this behavior in a level. Add it to a generic "UI" object

//
// Radar - Was originally based off community script, but now completely rewritten
//


using UnityEngine;
using System.Collections;

public class IconRadar : MonoBehaviour
{

    public enum IconRadarType : int { FullCircle };

    // Display Location
    public Vector2 radarLocationCustom;
    public IconRadarType radarType = IconRadarType.FullCircle;
    public GUISkin gSkin;

    public float labelYoffset = 32;  // The amount of the screen the radar will use
    public float labelLength = 100;
    public float labelHeight = 32;

    public float yoffset = 1.0f;
    public float minRadarDistance = 1.0f;

    //searching objects

    public static Hashlist searchableItems = new Hashlist();
    public static Hashlist searchingItems = new Hashlist();
    public Rect searchingGuiScreenRect = new Rect(0, 0, 0, 0);
    public Rect searchingGuiButtonRect = new Rect(0, 0, 100, 32);
    private bool showSearchWindow = false;
    private Vector2 scrollPos = new Vector2();


    // Blip information

    public Texture2D radarBlip1Icon;
    public string radarBlip1Tag;

    public Texture2D radarBlip2Icon;
    public string radarBlip2Tag;

    public Texture2D radarBlip3Icon;
    public string radarBlip3Tag;

    public Texture2D radarBlip4Icon;
    public string radarBlip4Tag;

    private GameObject _centerObject;



    // Initialize the radar
    void Start()
    {
        // Get our center object
        _centerObject = this.gameObject;
    }

    Texture2D GetIconForObject(Transform t)
    {
        if (t.tag == radarBlip1Tag)
        {
            return radarBlip1Icon;
        }
        else if (t.tag == radarBlip2Tag)
        {
            return radarBlip2Icon;
        }
        else if (t.tag == radarBlip3Tag)
        {
            return radarBlip3Icon;
        }
        else if (t.tag == radarBlip4Tag)
        {
            return radarBlip4Icon;
        }
        else return null;
    }

    // OnGUI is twice per frame
    void OnGUI()
    {
        if (gSkin != null)
            GUI.skin = gSkin;

        GUILayout.BeginArea(searchingGuiButtonRect);
        if (GUILayout.Button("Search", "Search"))
        {
            showSearchWindow = !showSearchWindow;
        }
        GUILayout.EndArea();

        if (showSearchWindow)
            searchingGuiScreenRect = GUILayout.Window(7337, searchingGuiScreenRect, SearchWindow, "Look For Objects");
        
        
            
        

        foreach (string k in searchingItems.GetList().Keys)
        {

            Transform t = searchingItems.GetItemFromList(k);

            Color color = Color.white;
            Texture2D icon;
            if (t == null)
                continue;

            if ((icon = GetIconForObject(t)) == null)
            {
                continue;
            }
            GameObject go = t.gameObject;
            {
                updateBlip(go, color, icon);
            }

        }

    }

    void SearchWindow(int windowID)
    {
        
        

        if (!searchableItems.IsEmpty())
        {

            
            GUILayout.Label("Select which objects below..");
            GUILayout.Label("They will show up in the world as icons to follow");
            scrollPos =  GUILayout.BeginScrollView(scrollPos);
            GUILayout.BeginVertical();

            foreach (string k in searchableItems.GetList().Keys)
            {

                Transform t = searchableItems.GetItemFromList(k);
                if (t != null)
                {
                    GUILayout.BeginHorizontal();
                    if (searchingItems.DoesItemExist(k))
                        GUILayout.Box(GetIconForObject(t), GUILayout.Width(32));
                    if (GUILayout.Toggle(searchingItems.DoesItemExist(k), t.gameObject.name))
                    {
                        
                        if (!searchingItems.DoesItemExist(k))
                            searchingItems.AddItemToList(k, t);
                    }
                    else
                    {
                        if (searchingItems.DoesItemExist(k))
                            searchingItems.RemoveItemFromList(k);
                    }
                    GUILayout.EndHorizontal();

                }
                else
                {
                    //GUILayout.Label("GUID :'" + k + "' transform empty");
                }
            }
           
            GUILayout.EndVertical();
            GUILayout.EndScrollView();
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Clear All"))
            {
                searchingItems.Clear();
            }
            if (GUILayout.Button("Close"))
            {
                showSearchWindow = false;
            }
            
            GUILayout.EndHorizontal();
        }
    }


    // Draw a blip for an object
    void updateBlip(GameObject go, Color blipcolor, Texture2D icon)
    {
        if (_centerObject && icon != null)
        {
            //Vector3 centerPos = _centerObject.transform.position;
            Vector3 extPos = go.transform.position;

            // Get the distance to the object from the centerObject
            //float dist = Vector3.Distance(centerPos, extPos);

            //float scalef = dist / radarMaxDistance;

            //Vector3 unit = (extPos - centerPos).normalized;
            //Vector3 pos = centerPos + unit * (radarSize * scalef) + minRadarDistance * unit;

            Vector3 pos = extPos;
            pos = new Vector3(pos.x, _centerObject.transform.position.y + yoffset, pos.z);

            Vector3 screenpos = Camera.main.WorldToScreenPoint(pos);
            Rect screenpos_i = new Rect(screenpos.x, Camera.main.pixelHeight - screenpos.y, icon.width, icon.height);
            Rect screenpos_l = new Rect(screenpos.x, Camera.main.pixelHeight - screenpos.y + labelYoffset, labelLength, labelHeight);

            if (screenpos.z > 0)
            {
                GUI.DrawTexture(screenpos_i, icon);

                if (screenpos_i.Contains(Event.current.mousePosition)) GUI.Label(screenpos_l, go.name); ;


            }

        }
    }

    public static void Refresh()
    {
        Hashtable newList = new Hashtable();

        foreach (string k in searchingItems.GetList().Keys)
        {
            Transform t = searchingItems.GetItemFromList(k);
            if (t != null)
                newList.Add(k, t);
        }

        searchingItems.OverriteList(newList);
    }

    void Awake()
    {
        searchableItems.Clear();
        searchingItems.Clear();
    }

}

Followed By the ItemSearchable C# behavior script
Add this behavior to objects that you want to appear in the searchable list. Note the trigger on OnDestroy() to handle cleanup should the object be removed from the game.


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class ItemSearchable : MonoBehaviour
{
    
    string GUID = "";

    public string base64Encode(string data)
    {
        try
        {
            byte[] encData_byte = new byte[data.Length];
            encData_byte = System.Text.Encoding.UTF8.GetBytes(data);
            string encodedData = Convert.ToBase64String(encData_byte);
            return encodedData;
        }
        catch (Exception e)
        {
            throw new Exception("Error in base64Encode" + e.Message);
        }
    }

    void Start()
    {
        GUID = base64Encode(this.gameObject.name);
        

        if (!IconRadar.searchableItems.DoesItemExist(GUID))
        {
            IconRadar.searchableItems.AddItemToList(GUID, this.gameObject.transform);
            //Debug.Log("Added item to searchable list: '" + this.gameObject.name + "'");
        }
        else
        {
            Debug.Log("Duplicate Named Item found when adding item to searchable list: '" + this.gameObject.name + "', GUID='" + GUID + "'");
        }
    }

    void OnDestroy()
    {
        if (IconRadar.searchableItems.DoesItemExist(GUID))
        {
            IconRadar.searchableItems.RemoveItemFromList(GUID);
        }
        IconRadar.Refresh();
    }

    public string GetTag()
    {
        return this.gameObject.tag;
    }

}

One kind of "3d" radar for Unity3D

3D Radar working in Unity3D
I wanted to take the radar script that is freely available on the Unity3D wiki page here and convert it from a traditional 2D representation to a 3d one - A radar with blips that float around your character.

As you can see in the above image, when you attach this script to a gameobject (and supply a prefab - in this case a basic sphere) the script will search for objects with a tag and color the spheres appropriately for the tag, moving them around the character to indicated the position of objects from the character. In the above case, blue dots are pointing to NPCs, and green dots to objects tagged "POI" (point of interest).

C# Script follows:



//
// Radar - Based off of the other radar scripts I've seen on the wiki
// but added 3d support for a 3d radar in the vein of the noise radar in metal gear solid 4
// only with easy prefabs instead of the full 'noise' idea. It wouldnt be hard to make the noise idea
// based off this script.
// By: Jason Lambert
// email: shotgunkiwi@gmail.com
//




using UnityEngine;
using System.Collections;


public class RealRadar : MonoBehaviour
{


    public enum RealRadarType : int { FullCircle }; //just one type for now


    // Display Location
    public Vector2 radarLocationCustom;
    public RealRadarType radarType = RealRadarType.FullCircle;
   
    public float radarSize = 0.20f;  // The amount of the screen the radar will use
    public float radarMaxDistance = 10.00f;
    public float yoffset = 1.0f;
    public int maxBlips = 100;
    public float minRadarDistance = 1.0f;
    public Transform blipPrefab = null;


    // Blip information
    public bool radarBlip1Active;
    public Color radarBlip1Color = new Color(0, 0, 255);
    public string radarBlip1Tag;


    public bool radarBlip2Active;
    public Color radarBlip2Color = new Color(0, 255, 0);
    public string radarBlip2Tag;


    public bool radarBlip3Active;
    public Color radarBlip3Color = new Color(255, 0, 0);
    public string radarBlip3Tag;


    public bool radarBlip4Active;
    public Color radarBlip4Color = new Color(255, 0, 255);
    public string radarBlip4Tag;


    // Internal vars
    private Vector2 _radarCenter;


    private GameObject _centerObject;


    private ArrayList blips = new ArrayList();






    // Initialize the radar
    void Start()
    {


        // Get our center object
        _centerObject = this.gameObject;


        if (blipPrefab != null)
        {
            for (int i = 0; i < maxBlips; i++)
            {
                Transform blip = (Transform.Instantiate(blipPrefab) as Transform);
                blip.localPosition = Vector3.zero;
                blip.parent = _centerObject.transform;
                blips.Add(blip);
                blip.gameObject.renderer.enabled = false;
            }
        }
    }


    // Update is called once per frame
    void Update()
    {
        GameObject[] gos;
        int blipNo = 0;


        


        // Position blips
        if (radarBlip1Active)
        {
            // Find all game objects
            gos = GameObject.FindGameObjectsWithTag(radarBlip1Tag);


            // Iterate through them and call drawBlip function
            foreach (GameObject go in gos)
            {
                updateBlip(go, radarBlip1Color, blips[blipNo++] as Transform);
            }
        }
        if (radarBlip2Active)
        {
            gos = GameObject.FindGameObjectsWithTag(radarBlip2Tag);


            foreach (GameObject go in gos)
            {
                updateBlip(go, radarBlip2Color, blips[blipNo++] as Transform);
            }
        }
        if (radarBlip3Active)
        {
            gos = GameObject.FindGameObjectsWithTag(radarBlip3Tag);


            foreach (GameObject go in gos)
            {
                updateBlip(go, radarBlip3Color, blips[blipNo++] as Transform);
            }
        }
        if (radarBlip4Active)
        {
            gos = GameObject.FindGameObjectsWithTag(radarBlip4Tag);


            foreach (GameObject go in gos)
            {
                updateBlip(go, radarBlip4Color, blips[blipNo++] as Transform);
            }
        }




        for (int i = blipNo; i < maxBlips; i++ )
        {
            (blips[i] as Transform).gameObject.renderer.enabled = false;
        }




    }


    // Draw a blip for an object
    void updateBlip(GameObject go, Color blipcolor, Transform blip)
    {
        if (_centerObject)
        {
            Vector3 centerPos = _centerObject.transform.position;
            Vector3 extPos = go.transform.position;


            // Get the distance to the object from the centerObject
            float dist = Vector3.Distance(centerPos, extPos);


            float scalef = dist / radarMaxDistance;
            //if (scalef < 1.0f) //uncomment this section if you want to use the maxdistance property
            //{
                blip.renderer.enabled = true;
                Vector3 unit = (extPos - centerPos).normalized;
                blip.position = centerPos + unit * (radarSize * scalef) + minRadarDistance * unit;
                blip.position = new Vector3(blip.position.x, _centerObject.transform.position.y + yoffset, blip.position.z);
                blip.renderer.material.SetColor("_Color", blipcolor);
            //} //uncomment this section if you want to use the maxdistance property
            //else 
            //{
            //    blip.renderer.enabled = false;
            //}//
            
        }
    }


   


}



Small Utility Class for Unity3D

To avoid the costly gameobject.find() method I wanted a way to register objects into a "database" that could be queried at o(1) complexity. To do that, you need to generate unique IDs for objects, a GUID if you will. A hashtable is a great collection to do this with - so I wanted to write a simple wrapper for a hashtable ( I will call it hashlist) that would make code easier to read.

I use this class to hold 'databases' of run-time objects in several classes. Remotely connected players, Network synchronized items and states, as well as a list of objects that are currently being 'inspected' by the player.

This is a generic implementation to accept Transforms which will need to be casted to the correct object type on retrieval.

This class will be used by other examples in newer posts.


using System;
using System.Collections;
using UnityEngine;


    //Class to store list of transforms to facilitate easy retrieval
    public class Hashlist
    {
        //List of NPCs in the World
        private Hashtable List = new Hashtable();


        public Hashtable GetList()
        {
            return List;
        }


        public void OverriteList(Hashtable newlist)
        {
            List = newlist;
        }


        public string AddItemToList(string guid, Transform obj)
        {
            if (guid == "")
                guid = obj.name + obj.position.x;
            List.Add(guid, obj);


            //Debug.Log("added item to list with guid: " + guid);
            return guid;
        }
        public void RemoveItemFromList(string guid)
        {
            List.Remove(guid);
        }
        public Transform GetItemFromList(string guid)
        {
            return (Transform)List[guid];
        }
        public bool DoesItemExist(string guid)
        {
            return List.ContainsKey(guid);
        }
        public int Clear()
        {
            int deleted = 0;
            List.Clear();


            return deleted;
        }
        public bool IsEmpty()
        {
            return (List.Count == 0);
        }
    }

Tuesday, July 19, 2011

Unity3 and web services

Introduction
We wanted to be able to stream data into our Unity3D environment from our joomla website. I have rewritten the component from the ground-up twice but I thought I would share my design methodology (and technology) with the masses.

The humble backbone through all the iterations has been the WWW class (docs). By sending GET (and later POST) requests to the server, I would serve back information about articles, resources, images and other content. To being with, I wrote a couple extra methods in our joomla server's resource controller that send back the number of resources, the path to files. But this was all plain unformatted text, and one response per attribute - it was tiresome to use. I then moved to creating an XML structured response, and unity would then parse the XML. But because the XML readers required additional code, and I was trying to keep the codebase as lean as possible, I moved to JSON. A nice JSON reader comes prepackaged with the unity3 API for smartfox server - and I had already built a JSON web services API for a google web toolkit client... so the match was made in heaven - I was going to build a JSON web services api.

The result:
Picture! Images dynamically downloaded into Unity based on web services


So what follows is a big chunk of semi-source code of how that is integrated to Unity. The basic flow is:


  • a UnityGameObject starts a coroutene asking for web data
  • Generate request inside unity, and send using WWW class
  • Server receives call, retrieves data from database, creates an encode a PHP object to JSON, sends this as response
  • Unity receives object, decodes using libJSON, calls back original UnityGameObject with data



Example client functions for send request and decode response (C#):


    //Download and add image to billboard
    public static IEnumerator DownloadResource(int id, IHasResourceResult attachpoint)
    {


        WWW request = new WWW(baseURL() + hubCommandBaseUrl + "&task=jsonlist&jtask=detail&rid=" + id.ToString() + hubNoHtmlUrl);
//formats a url that look similar to:
//http://myserver/option=com_resource&task=jsonlist&jtask=detail&rid=1000&no_html=1
            yield return request;


            
            //attach resource callback
            //This is inside a loader help class, and called via a coroutene with a self reference passed as a callback
            //we need to callback the caller and give them the resource          attachpoint.AttachResource(loadFromJSON(request.text.Replace("[","").Replace("]","")));




            yield return null;
       
    }


//decode the JSON data

    private static ResourceResult loadFromJSON(String JSON)
    {
        LitJson.JsonData jResponse = LitJson.JsonMapper.ToObject(JSON);
        ResourceResult result = new ResourceResult();


        result.introtext = Escape((string)jResponse["introtext"]);
        result.id = (int) jResponse["id"];
        result.title = Escape((string)jResponse["title"]);
        result.thumbnail = Escape((string)jResponse["image"]);
        result.document = Escape((string)jResponse["document"]);


        return result;
    }



Example server functions to send response (PHP):

   //encode objects into JSON
function JSONObj($object)
{
$json =  json_encode($object);
$json = preg_replace( "/\"(\d+)\"/", '$1', $json );

return $json;
}
function startList()
{
echo '[ ';
}
function endList()
{
echo ' ]';
}

//called by controller to switch off to correct task
function JSONResponse($task)
{
switch ($task)
{
//switch off to task function
                        default:
                          $this->exampletaskfunction()
}
}

function exampletaskfunction()
{
$database =& JFactory::getDBO();
$id = JRequest::getInt( 'rid', 0 );
$resource = GET_RESOURCE_DATA_FROM_DATABASE()

                                       //make an object to convert into JSON
$obj = array();
$obj['introtext'] = htmlentities($resource->introtext);
$obj['image'] = '';
$obj['title'] = $resource->title;
$obj['id'] = $resource->id;
$obj['fulltext'] = $resource->fulltext? htmlentities($resource->fulltext): '' ;
$obj['document'] = '';


$this->startList();
echo $this->JSONObj($obj)."\n";
$this->endList();


}


Example JSON response data from server:

[ {"introtext":"&amp;nbsp;\n\n\t&amp;nbsp;The primary purpose of this paper is to present the instrumentation plan of a full&acirc;","image":"2011\/07\/03081\/.thumb\/.thumbfile.1.jpg","title":"SEISMIC RESPONSE OF STRUCTURAL PILE\u2010WHARF DECK CONNECTIONS FOR PORT STRUCTURES ","id":3080,"fulltext":"\t&lt;p&gt;\n\t&amp;nbsp;&lt;\/p&gt;\n&lt;p&gt;\n\t&amp;nbsp;The primary purpose of this paper is to present the instrumentation plan of a full&acirc;","document":"MjAxMS8wNy8wMzA4MS8ud2Vidmlldy8ud2Vidmlldy5zd2Y="}
 ]

Conclusion
So that shows a basic concept of how to program a web service architecture into unity.