Friday, August 12, 2011

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;
    }

}

No comments:

Post a Comment