/// <summary>
    /// returns the surface type of a single material and no texture regions,
    /// by raycast hit point
    /// </summary>
    protected static vp_SurfaceType GetSimpleSurface(RaycastHit hit)
    {
        if (hit.collider == null)
        {
            return(null);
        }

        vp_SurfaceType s = null;

        if (!m_SurfacesTypesByCollider.TryGetValue(hit.collider, out s))
        {
            if (!HasMultiMaterial(hit.collider))
            {
                Texture tex = GetMainTexture(hit);
                if ((tex != null) && (!IsUVTexture(tex)))
                {
                    s = GetNonUVSurface(tex);
                }
            }

            // associate final result to this collider from now on, whether null or not
            m_SurfacesTypesByCollider.Add(hit.collider, s);
        }

        return(s);
    }
    /// <summary>
    /// composes a dictionary of all the terrain textures in the level
    ///	and their associated surface types
    /// </summary>
    protected void InitTerrainSurfaces()
    {
        List <Texture> levelTerrainTextures = GetLevelTerrainTextures();

        if (levelTerrainTextures == null)
        {
            return;
        }

        foreach (Texture tex in levelTerrainTextures)
        {
            // see if this terrain texture has been added to the surface manager as a NON-UV texture
            vp_SurfaceType surfaceType = GetNonUVSurface(tex);

            // if not, complain with a warning and skip it
            if (surfaceType == null)
            {
                if (IsUVTexture(tex))
                {
                    Debug.LogWarning("Warning (SurfaceManager) Terrain texture '" + tex.name + "' has an UV region in the Surface Manager. UV regions do not work with terrain textures. Ground with this texture will fallback to the default surface type.");
                }
                else
                {
                    Debug.LogWarning("Warning (SurfaceManager) Terrain texture '" + tex.name + "' has not been added to the Surface Manager. Ground with this texture will fallback to the default surface type.");
                }
                continue;
            }

            // if so, add this surface type to the terrain surface dictionary
            m_SurfacesByTerrainTexture.Add(tex, surfaceType);
        }
    }
Example #3
0
    public static void CreateVpSurfaceType()
    {
        vp_SurfaceType asset = (vp_SurfaceType)vp_EditorUtility.CreateAsset("UFPS/Base/Content/Surfaces/SurfaceTypes", typeof(vp_SurfaceType));

        if (asset != null)
        {
            asset.Init();
        }
    }
    /// <summary>
    /// returns the surface type at a raycast hit point. detects surfaces by
    /// surface identifier, texture / material, atlas texture and terrain
    /// </summary>
    public static vp_SurfaceType GetSurfaceType(RaycastHit hit)
    {
        // TIP: if you have problems getting the correct footstep- or projectile
        // FX to spawn on an object, uncomment all the debug lines in this method
        // to see what 'GetSurfaceType' detects (if anything)

        vp_SurfaceType s = null;

        // --- surface identifier ---
        // detect objects with a surface identifier
        vp_SurfaceIdentifier i = GetSurfaceIdentifier(hit);

        if (i != null)
        {
            s = i.SurfaceType;
            //Debug.Log("hit a surface identifier '" + s .name + "' @ " + Time.time);
            if (s != null)
            {
                return(s);
            }
        }

        // --- simple surface ---
        // detect objects with a single material and no texture regions
        s = GetSimpleSurface(hit);
        if (s != null)
        {
            //Debug.Log("hit a single material object with surface '" + s.name + "' @ " + Time.time);
            return(s);
        }

        // --- complex surface ---
        // detect objects with texture regions (atlases) and / or multiple materials
        s = GetComplexSurface(hit);
        if (s != null)
        {
            //Debug.Log("hit a multi-material (or uv region fallback) object with surface '" + s.name + "' @ " + Time.time);
            return(s);
        }

        // --- terrain surface ---
        // check the terrain for a surface if all of the above failed
        s = GetTerrainSurface(hit);
        if (s != null)
        {
            //Debug.Log("hit a terrain object with surface '" + s.name + "' @ " + Time.time);
            return(s);
        }

        // --- no surface ---
        // failed to find a surface based on the raycast hit
        //Debug.Log("failed to find a surface on object '"+hit.transform.gameObject.name+"'");
        return(null);
    }
    /// <summary>
    /// spawns the footprint effect resulting from the 'impactEvent' + 'surface'.
    /// 'footprintDirection' should be the forward vector of the foot. if
    /// 'footprintFlip' is true, the decal's X-scale will be inverted. if
    /// 'footprintVerifyGroundContact' is true (not recommended) the decal
    /// system will perform four extra raycasts per footstep (!) to verify
    /// ground contact
    /// </summary>
    public static void SpawnFootprintEffect(RaycastHit hit, vp_SurfaceType surface, vp_ImpactEvent impactEvent, Vector3 footprintDirection, bool footprintFlip, bool footprintVerifyGroundContact = false, AudioSource audioSource = null, bool allowDecal = true)
    {
        // set footprint flags on the vp_SurfaceEffect system before spawning
        vp_SurfaceEffect.FootprintDirection           = footprintDirection;
        vp_SurfaceEffect.FootprintFlip                = footprintFlip;
        vp_SurfaceEffect.FootprintVerifyGroundContact = footprintVerifyGroundContact;

        SpawnEffect(hit, impactEvent, surface, audioSource, allowDecal);

        vp_SurfaceEffect.FootprintDirection           = vp_SurfaceEffect.NO_DIRECTION;
        vp_SurfaceEffect.FootprintVerifyGroundContact = false;
    }
Example #6
0
    /// <summary>
    /// tries to play the sound related to this object and 'surfaceType'.
    /// if none found, reverts to fallbacks
    /// </summary>
    public bool TryPlaySound(vp_SurfaceType surfaceType)
    {
        if (surfaceType == null)
        {
            return(false);
        }

        if (Audio2 == null)
        {
            return(false);
        }

        // don't allow audiosources in 'Logarithmic Rolloff' mode to be audible
        // beyond their max distance (Unity bug?)
        if (Vector3.Distance(transform.position, UnityEngine.Camera.main.transform.position) > Audio2.maxDistance)
        {
            return(false);
        }

        if (!vp_Utility.IsActive(gameObject))
        {
            return(false);
        }

        bool playedSound = false;
        int  fallback    = -1;

        for (int v = CollisionSounds.Count - 1; v > -1; v--)
        {
            if (surfaceType == CollisionSounds[v].SurfaceType)
            {
                DoPlaySound(CollisionSounds[v].Sound);
                playedSound = true;
            }
            else if (m_HaveSurfaceManager && (CollisionSounds[v].SurfaceType == vp_SurfaceManager.Instance.Fallbacks.SurfaceType))
            {
                fallback = v;
            }
        }

        if (!playedSound && (fallback >= 0))
        {
            DoPlaySound(CollisionSounds[fallback].Sound);
        }

        return(true);
    }
Example #7
0
    /// <summary>
    /// returns the surface effect stored in 'surfaceType' under 'impact'
    /// (or null if no match)
    /// </summary>
    protected static vp_SurfaceEffect GetPrimaryEffect(vp_SurfaceType surfaceType, vp_ImpactEvent impact)
    {
        Dictionary <vp_ImpactEvent, vp_SurfaceEffect> impacts = GetImpactFXDictionary(surfaceType);

        if (impacts == null)
        {
            return(null);
        }

        if (impacts.Count == 0)
        {
            return(null);
        }

        vp_SurfaceEffect fx;

        impacts.TryGetValue(impact, out fx);
        return(fx);
    }
    /// <summary>
    /// spawns the effect resulting from 'impactEvent' + 'surface'. if 'surface'
    /// is null, derives it from the raycast hit
    /// </summary>
    public static bool SpawnEffect(RaycastHit hit, vp_ImpactEvent impact, vp_SurfaceType surface = null, AudioSource audioSource = null, bool allowDecal = true)
    {
        // 'surface' is optional. if we don't already know it we try to derive it from the RaycastHit
        if (surface == null)
        {
            surface = GetSurfaceType(hit);              // if this returns null we will rely on fallbacks
        }
        if (allowDecal)
        {
            // a surface identifier can always force 'allowdecal' to false
            allowDecal = AllowsDecals(hit.collider);

            // test against stretched decals on non-uniform objects
            if (allowDecal &&                                                   // if decal is allowed ...
                (vp_DecalManager.Instance != null) &&                           // ... and we have a decalmanager ...
                !vp_DecalManager.Instance.AllowStretchedDecals &&               // ... which is concered about stretching ...
                !hit.transform.gameObject.isStatic)                             // ... then unless the target is static ... (decals don't get childed to static objects so there won't be stretching)
            {
                // ... then only allow decal in case the object has uniform scale!
                // (but use an epsilon of '0.00001' in case there is a slight but insignificant scale difference)
                allowDecal = vp_MathUtility.IsUniform(hit.transform.localScale, 0.00001f);
            }
        }

        vp_SurfaceEffect fx = GetResultingEffect(impact, surface, ref allowDecal);

        if (fx == null)
        {
            return(false);
        }

        if (allowDecal)
        {
            fx.SpawnWithDecal(hit, audioSource);
        }
        else
        {
            fx.Spawn(hit, audioSource);
        }

        return(true);
    }
    /// <summary>
    /// retrieves the dictionary of impact effects stored in a certain surfacetype object
    /// </summary>
    protected static Dictionary <vp_ImpactEvent, vp_SurfaceEffect> GetImpactFXDictionary(vp_SurfaceType surface)
    {
        if (surface == null)
        {
            return(null);
        }

        //Debug.Log("surface : " + surface);

        Dictionary <vp_ImpactEvent, vp_SurfaceEffect> dict;

        if (!m_ImpactFXBySurface.TryGetValue(surface, out dict))
        {
            Dictionary <vp_ImpactEvent, vp_SurfaceEffect> impactFX = new Dictionary <vp_ImpactEvent, vp_SurfaceEffect>();
            for (int v = 0; v < surface.ImpactFX.Count; v++)
            {
                if (surface.ImpactFX[v].ImpactEvent == null)
                {
                    continue;
                }

                if (impactFX.ContainsKey(surface.ImpactFX[v].ImpactEvent))
                {
                    Debug.LogWarning("Warning (vp_SurfaceManager) Surface Type '" + surface + "' has more than one '" + surface.ImpactFX[v].ImpactEvent + "' added. Only the first one will be used.");
                    continue;
                }
                impactFX.Add(surface.ImpactFX[v].ImpactEvent, surface.ImpactFX[v].SurfaceEffect);
            }
            //Debug.Log("impactFX: " + impactFX);

            m_ImpactFXBySurface.Add(surface, impactFX);
            dict = impactFX;
        }

        return(dict);
    }
    /// <summary>
    /// arrives at the most suitable surface effect based on one combo
    /// of surfaceType + impactEvent (or predefined fallbacks) plus the
    /// merged 'allowDecal' settings (from the calling method, any surface
    /// identifiers and the effect itself)
    /// </summary>
    protected static vp_SurfaceEffect GetResultingEffect(vp_ImpactEvent impact, vp_SurfaceType surface, ref bool allowDecals)
    {
        m_UsingFallbackImpact  = false;
        m_UsingFallbackSurface = false;

        // if no IMPACT EVENT was provided - attempt to use fallback impact
        if ((impact == null) && (Instance != null))
        {
            impact = Instance.Fallbacks.ImpactEvent;
            m_UsingFallbackImpact = true;
        }

        if (impact == null)
        {
            return(null);
        }

        // if no SURFACE TYPE could be found - attempt to use fallback surface
        if ((surface == null) && (Instance != null))
        {
            surface = Instance.Fallbacks.SurfaceType;
            m_UsingFallbackSurface = true;
        }

        if (surface == null)
        {
            return(null);
        }

        if (surface.ImpactFX == null)
        {
            return(null);
        }

        if (surface.ImpactFX.Count == 0)
        {
            return(null);
        }

        vp_SurfaceEffect fx = GetPrimaryEffect(surface, impact);

        // if fx is null here and we are not using a fallback surface, it means the
        // level surface did not contain our impact event: so use fallback surface
        if ((fx == null) && !m_UsingFallbackSurface && (Instance != null))
        {
            surface = Instance.Fallbacks.SurfaceType;
            fx      = GetPrimaryEffect(surface, impact);
        }

        // if fx is null here, the detected surface does not recognize the impact
        // event, so try again with the SurfaceManager's fallback impact event
        // (this can solve cases where the surface is the fallback surface and the
        // impact type is a new, unknown impact type that has not been assigned to
        // anything)
        if (fx == null)
        {
            fx = GetPrimaryEffect(surface, Instance.Fallbacks.ImpactEvent);
        }

        // if fx is null here, we have nothing to work with: abort
        if (fx == null)
        {
            return(null);
        }

        // we have an effect! determine if it can be spawned with a decal
        // (global fallbacks are allowed to override 'allowDecals')
        if (allowDecals && (fx.Decal.m_Prefabs.Count > 0))
        {
            if (m_UsingFallbackSurface || m_UsingFallbackImpact)
            {
                allowDecals = Instance.Fallbacks.AllowDecals;
            }
        }
        else
        {
            allowDecals = false;                // spawn with sounds & objects only
        }

        return(fx);
    }
    /// <summary>
    ///
    /// </summary>
    public virtual void DoTextureFallbacksFoldout()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Space(10);

        m_TextureFallbacksFoldout = EditorGUILayout.Foldout(m_TextureFallbacksFoldout, "Texture Fallbacks");

        GUILayout.Space(10);
        GUILayout.EndHorizontal();

        if (m_TextureFallbacksFoldout)
        {
            GUILayout.Space(0);

            if (m_Component.ObjectSurfaces != null)
            {
                for (int i = 0; i < m_Component.ObjectSurfaces.Count; i++)
                {
                    vp_SurfaceManager.ObjectSurface surface = m_Component.ObjectSurfaces[i];

                    if (surface.UVTextures.Count == 0)
                    {
                        vp_SurfaceManager.UVTexture texture = new vp_SurfaceManager.UVTexture(true);
                        surface.UVTextures.Add(texture);
                    }

                    GUILayout.BeginHorizontal();
                    GUILayout.Space(20);
                    surface.Foldout = EditorGUILayout.Foldout(surface.Foldout, surface.Name);

                    if (GUILayout.Button("Remove", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(50), GUILayout.MaxWidth(50), GUILayout.MinHeight(15)))
                    {
                        m_Component.ObjectSurfaces.RemoveAt(i);
                        i--;
                    }
                    GUI.backgroundColor = Color.white;

                    GUILayout.Space(20);

                    GUILayout.EndHorizontal();

                    GUILayout.Space(5);

                    if (surface.Foldout)
                    {
                        GUILayout.BeginHorizontal();
                        GUILayout.Space(35);
                        vp_SurfaceType s = surface.SurfaceType;
                        surface.SurfaceType = (vp_SurfaceType)EditorGUILayout.ObjectField("SurfaceType", surface.SurfaceType, typeof(vp_SurfaceType), false);

                        if ((s != surface.SurfaceType))
                        {
                            for (int ii = 0; ii < m_Component.ObjectSurfaces.Count; ii++)
                            {
                                if (i != ii)
                                {
                                    if (m_Component.ObjectSurfaces[ii].SurfaceType == m_Component.ObjectSurfaces[i].SurfaceType)
                                    {
                                        EditorUtility.DisplayDialog("Ooops!", "The SurfaceType '" + surface.SurfaceType.name + "' has already been added. Please choose a different SurfaceType.", "OK");
                                        surface.SurfaceType = s;
                                    }
                                }
                            }
                        }

                        if ((surface.SurfaceType != null) && (s != surface.SurfaceType))
                        {
                            surface.Name = surface.SurfaceType.name;
                        }
                        if ((surface.SurfaceType != null) && GUILayout.Button("X", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(12), GUILayout.MaxWidth(12), GUILayout.MinHeight(12)))
                        {
                            surface.SurfaceType = null;
                        }
                        GUILayout.Space(20);
                        GUILayout.EndHorizontal();

                        if (surface.SurfaceType == null)
                        {
                            surface.Name = "Please assign a SurfaceType";
                        }

                        GUILayout.BeginHorizontal();
                        GUILayout.Space(38);

                        if (surface.TexturesFoldout)
                        {
                            surface.TexturesFoldout = EditorGUILayout.Foldout(surface.TexturesFoldout, "Textures", HeaderStyleSelected);
                        }
                        else
                        {
                            surface.TexturesFoldout = EditorGUILayout.Foldout(surface.TexturesFoldout, "Textures");
                        }

                        GUILayout.EndHorizontal();

                        if (surface.TexturesFoldout)
                        {
                            if (surface.UVTextures != null)
                            {
                                if (surface.UVTextures.Count > 0)
                                {
                                    int counter = 0;

                                    for (int x = 0; x < surface.UVTextures.Count; x++)
                                    {
                                        if (counter == 0)
                                        {
                                            GUILayout.BeginHorizontal(GUILayout.MinHeight(100));
                                            GUILayout.Space(50);
                                        }

                                        GUILayout.BeginVertical(GUILayout.MinHeight(90));
                                        GUILayout.Space(12);                                            // moves everything vertically

                                        GUILayout.BeginHorizontal(GUILayout.MaxWidth(75), GUILayout.MinWidth(75));

                                        // --- uv editor closed ---
                                        GUILayout.Space(8);                                             // left margin for lower buttons
                                        if (!surface.UVTextures[x].ShowUV)
                                        {
                                            GUILayout.FlexibleSpace();
                                            if (GUILayout.Button("X", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(12), GUILayout.MaxWidth(12), GUILayout.MinHeight(12)))
                                            {
                                                if (((x > 0) || ((x == 0) && (surface.UVTextures[0].Texture != null) || (surface.UVTextures.Count > 1))))
                                                {
                                                    if (surface.UVTextures[x].Texture != null)
                                                    {
                                                        surface.UVTextures[x].Texture = null;
                                                        vp_SurfaceManager.Instance.Reset();
                                                    }
                                                    else
                                                    {
                                                        surface.UVTextures.RemoveAt(x);
                                                        x = Mathf.Max(0, x - 1);
                                                        GUI.FocusControl(null);
                                                    }
                                                }
                                                //m_Component.SetDirty(true);
                                            }
                                            GUILayout.Space(-4);                                                // right margin for upper buttons
                                        }

                                        // --- uv editor open ---

                                        GUILayout.EndHorizontal();

                                        if (surface.UVTextures[x].Texture != null)
                                        {
                                            GUILayout.Space(44);
                                        }
                                        else
                                        {
                                            GUILayout.Space(63);
                                        }

                                        GUILayout.BeginHorizontal(GUILayout.MaxWidth(75), GUILayout.MinWidth(75));
                                        GUILayout.Space(4);                                             // left margin for upper buttons

                                        if (surface.UVTextures[x].Texture == null)
                                        {
                                            surface.UVTextures[x].ShowUV = false;
                                        }
                                        else if (!surface.UVTextures[x].ShowUV)
                                        {
                                            if (GUILayout.Button(("UV" + (surface.UVTextures[x].UV != m_Component.DefaultUV ? " *" : "")), vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(25), GUILayout.MaxWidth(25), GUILayout.MinHeight(13)))
                                            {
                                                surface.UVTextures[x].ShowUV = true;
                                            }
                                        }
                                        GUILayout.FlexibleSpace();

                                        GUILayout.Space(-4);                                            // right margin for upper buttons
                                        GUILayout.EndHorizontal();
                                        if (!surface.UVTextures[x].ShowUV)
                                        {
                                            GUILayout.Space(-63);
                                        }
                                        else
                                        {
                                            GUILayout.Space(-25);
                                        }

                                        GUILayout.Space(-17);                                           // moves texture + upper buttons vertically
                                        if (!surface.UVTextures[x].ShowUV)
                                        {
                                            surface.UVTextures[x].Texture = (Texture)EditorGUILayout.ObjectField(surface.UVTextures[x].Texture, typeof(Texture), false, GUILayout.MinWidth(75), GUILayout.MaxWidth(75), GUILayout.MinHeight(75), GUILayout.MaxHeight((surface.UVTextures[x].ShowUV ? 75 : 75)));
                                        }
                                        if (surface.UVTextures[x].ShowUV)
                                        {
                                            Rect v = surface.UVTextures[x].UV;
                                            GUILayout.BeginVertical(GUILayout.MaxWidth(75), (GUILayout.MinWidth(75)));
                                            GUILayout.BeginHorizontal();
                                            GUILayout.FlexibleSpace();
                                            GUI.SetNextControlName("reset");
                                            if (GUILayout.Button("Reset UV", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinHeight(15), GUILayout.MaxWidth(75)))
                                            {
                                                surface.UVTextures[x].UV = m_Component.DefaultUV;
                                                // focus the reset button to get rid of possible textfield focus
                                                // or the textfields won't update properly when resetting
                                                GUI.FocusControl("reset");
                                                GUI.FocusControl(null);                                                 // unfocus or button highlight may go weird
                                            }
                                            GUILayout.FlexibleSpace();
                                            GUILayout.EndHorizontal();
                                            GUILayout.Space(4);
                                            GUILayout.BeginHorizontal(GUILayout.MaxWidth(75), GUILayout.MinWidth(75));

                                            surface.UVTextures[x].UV = EditorGUILayout.RectField(surface.UVTextures[x].UV, GUILayout.MinWidth(95), GUILayout.MaxWidth(95));
                                            if (v != surface.UVTextures[x].UV)
                                            {
                                                surface.UVTextures[x].UV = new Rect(
                                                    Mathf.Clamp(surface.UVTextures[x].UV.xMin, 0, 1),
                                                    Mathf.Clamp(surface.UVTextures[x].UV.yMin, 0, 1),
                                                    Mathf.Clamp(surface.UVTextures[x].UV.width, 0, 1),
                                                    Mathf.Clamp(surface.UVTextures[x].UV.height, 0, 1)
                                                    );
                                            }
                                            GUILayout.EndHorizontal();

                                            GUILayout.BeginHorizontal();
                                            GUILayout.FlexibleSpace();

                                            if (GUILayout.Button("Show Texture", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(75), GUILayout.MaxWidth(75), GUILayout.MinHeight(15)))
                                            {
                                                surface.UVTextures[x].ShowUV = !surface.UVTextures[x].ShowUV;
                                            }
                                            GUILayout.FlexibleSpace();
                                            GUILayout.EndHorizontal();
                                            GUILayout.EndVertical();
                                        }

                                        GUILayout.Space(-80);                                           // top margin for upper buttons

                                        GUILayout.BeginHorizontal(GUILayout.MaxWidth(75), GUILayout.MinWidth(75));
                                        GUILayout.Space(8);                                             // left margin for upper buttons

                                        // --- uv 2 ---
                                        if (!surface.UVTextures[x].ShowUV)
                                        {
                                            if ((x == 0) && ((surface.UVTextures[0].Texture == null) && (surface.UVTextures.Count == 1)))
                                            {
                                                GUI.color = Color.clear;
                                            }
                                            GUILayout.FlexibleSpace();
                                            GUILayout.Button("X", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(12), GUILayout.MaxWidth(12), GUILayout.MinHeight(12));
                                            GUILayout.Space(-4);                                                // right margin for lower buttons
                                            GUI.color = Color.white;
                                        }

                                        GUI.backgroundColor = Color.white;

                                        GUILayout.EndHorizontal();
                                        GUILayout.Space(43);

                                        GUILayout.BeginHorizontal(GUILayout.MaxWidth(75), GUILayout.MinWidth(75));
                                        GUILayout.Space(4);                                             // left margin for upper buttons

                                        if (surface.UVTextures[x].Texture == null)
                                        {
                                            surface.UVTextures[x].ShowUV = false;
                                        }
                                        else if (!surface.UVTextures[x].ShowUV)
                                        {
                                            GUILayout.Button(("UV" + (surface.UVTextures[x].UV != m_Component.DefaultUV ? " *" : "")), vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(25), GUILayout.MaxWidth(25), GUILayout.MinHeight(13));
                                        }
                                        GUILayout.FlexibleSpace();
                                        if (!surface.UVTextures[x].ShowUV)
                                        {
                                            GUILayout.Button("Select", vp_EditorGUIUtility.SmallButtonStyle, GUILayout.MinWidth(35), GUILayout.MaxWidth(35), GUILayout.MinHeight(13));
                                        }

                                        GUILayout.Space(-4);                                            // right margin for upper buttons
                                        GUILayout.EndHorizontal();

                                        GUILayout.EndVertical();

                                        counter++;

                                        if (counter == 4 || x == surface.UVTextures.Count - 1)
                                        {
                                            GUILayout.Space(20);

                                            GUILayout.EndHorizontal();
                                            counter = 0;
                                        }
                                    }
                                }
                            }

                            GUILayout.BeginHorizontal();
                            GUILayout.FlexibleSpace();
                            if (GUILayout.Button("Add Texture Slot", GUILayout.MinWidth(120), GUILayout.MaxWidth(120)))
                            {
                                vp_SurfaceManager.UVTexture texture = new vp_SurfaceManager.UVTexture(true);
                                surface.UVTextures.Add(texture);
                            }
                            GUILayout.Space(20);
                            GUI.backgroundColor = Color.white;
                            GUILayout.EndHorizontal();
                        }

                        vp_EditorGUIUtility.Separator();

                        GUILayout.Space(5);
                    }
                }
            }

            if (m_Component.m_ShowHelp && (m_Component.ObjectSurfaces.Count == 0))
            {
                GUILayout.BeginHorizontal();
                GUILayout.Space(50);
                EditorGUILayout.HelpBox("There are no Texture Groups. Click the \"Add Texture Groups\" button to add one.", MessageType.Info);
                GUILayout.Space(20);
                GUILayout.EndHorizontal();
            }

            GUILayout.Space(8);

            GUILayout.BeginHorizontal();

            GUILayout.Space(10);

            if (GUILayout.Button("Add Texture Group", GUILayout.MinWidth(150), GUILayout.MinHeight(25)))
            {
                vp_SurfaceManager.ObjectSurface surface = new vp_SurfaceManager.ObjectSurface();
                m_Component.ObjectSurfaces.Add(surface);
            }
            GUI.backgroundColor = Color.white;
            GUILayout.Space(20);
            GUILayout.EndHorizontal();

            GUILayout.Space(10);

            if (m_Component.m_ShowHelp)
            {
                GUILayout.BeginHorizontal();
                GUILayout.Space(10);
                GUI.enabled = false;
                EditorGUILayout.HelpBox("• To create a new SurfaceType fallback for a texture set, click 'Add Texture Group' and assign the vp_SurfaceType. Then, add all the textures you want associated with that particular surface type.\n\n• You can click the 'UV' button to restrict the surface inside a texture. Note that if you want to have several surfaces inside a single texture, you need to add the texture once for every UV region.\n", MessageType.None);
                GUI.enabled = true;
                GUILayout.Space(20);
                GUILayout.EndHorizontal();
            }

            vp_EditorGUIUtility.Separator();
        }
    }
    /// <summary>
    /// places a footstep effect on the ground using the foot, raycast
    /// hit, footprint flip, audiosource and other settings assigned by the
    /// previously executed methods. also, updates next allowed footstep
    /// time for the timed interval mode, and yaw for the 'IsRotating'
    /// property
    /// </summary>
    protected virtual void DoStep()
    {
        // mute is a debug feature (for tweaking footstep ranges in multiplayer)
        if (m_Mute)
        {
            return;
        }

        // --- play effects using the surface system ---

        // first retrieve the surface type, so see if it supports footprints.

        if (FootstepImpactEvent != null)
        {
            vp_SurfaceType surface = vp_SurfaceManager.GetSurfaceType(m_Hit);
            //Debug.Log("surface: " + surface);

            if (!IsMoving || ((surface == null) || !surface.CanHaveFootprints()))
            {
                // non-footrint surface: place a simple effect supporting just audio and particles
                vp_SurfaceManager.SpawnEffect(m_Hit, FootstepImpactEvent, surface, m_CurrentAudioSource, false);

                // NOTES:
                // 1) if surface is null, a fallback surface will be used
                // 2) checking for 'IsMoving' prevents spawning hundreds of footprint decals under
                // a player who may be just standing still mouselooking
            }
            else
            {
                // footprint surface: place the effect with direction, movement and footprint flip info
                vp_SurfaceManager.SpawnFootprintEffect(m_Hit, surface, FootstepImpactEvent,
                                                       m_CurrentFoot.transform.forward, m_FlipFootprint, VerifyGroundContact, m_CurrentAudioSource);
            }
        }

        // --- update misc variables for next frame ---

        // for timed interval mode
        if (Mode == (int)FootstepMode.FixedTimeInterval)
        {
            if (!IsMoving && IsRotating)
            {
                m_NextAllowedFootstepTime = Time.time + (TimeInterval * Random.Range(1.0f, 2.0f));                      // make foosteps a bit irregular standing still and rotating
            }
            else
            {
                m_NextAllowedFootstepTime = Time.time + (TimeInterval / Mathf.Max(0.2f, Player.InputMoveVector.Get().magnitude));                       // dividing by input move vector allows slowing down the footsteps using an analog controller
            }
        }

        // for 'IsRotating' property
        m_PrevYaw = transform.eulerAngles.y;

        // if debugging with the pause feature, pause the editor now
#if UNITY_EDITOR
        if (PauseOnEveryStep)
        {
            UnityEditor.EditorApplication.isPaused = true;
            Debug.Log("Debug (" + this + ") " + (m_FlipFootprint ? " Right " : " Left ") + " footstep triggered @ " + Time.time);
        }
#endif
    }