VR开发实战HTC Vive项目之八大恒星(射线的管理与使用)

一、框架视图

二、主要代码

VRTK_OutlineObjectCopyHighlighter

// Outline Object Copy|Highlighters|40030
namespace VRTK.Highlighters
{
    using UnityEngine;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Creates a mesh copy and applies an outline shader which is toggled on and off when highlighting the object.
    /// </summary>
    /// <remarks>
    ///   > A valid mesh must be found or provided for the clone mesh to be created.
    ///
    /// **Script Usage:**
    ///  * Place the `VRTK_OutlineObjectCopyHighlighter` script on either:
    ///    * The GameObject of the Interactable Object to highlight.
    ///    * Any other scene GameObject and then link that GameObject to the Interactable Objects `Object Highlighter` parameter to denote use of the highlighter.
    ///  * Ensure the `Active` parameter is checked.
    /// </remarks>
    /// <example>
    /// `VRTK/Examples/005_Controller_BasicObjectGrabbing` demonstrates the outline highlighting on the green sphere when the controller touches it.
    ///
    /// `VRTK/Examples/035_Controller_OpacityAndHighlighting` demonstrates the outline highlighting if the left controller collides with the green box.
    /// </example>
    [AddComponentMenu("VRTK/Scripts/Interactions/Highlighters/VRTK_OutlineObjectCopyHighlighter")]
    public class VRTK_OutlineObjectCopyHighlighter : VRTK_BaseHighlighter
    {
        [Tooltip("The thickness of the outline effect")]
        public float thickness = 1f;
        [Tooltip("The GameObjects to use as the model to outline. If one isn't provided then the first GameObject with a valid Renderer in the current GameObject hierarchy will be used.")]
        public GameObject[] customOutlineModels;
        [Tooltip("A path to a GameObject to find at runtime, if the GameObject doesn't exist at edit time.")]
        public string[] customOutlineModelPaths;
        [Tooltip("If the mesh has multiple sub-meshes to highlight then this should be checked, otherwise only the first mesh will be highlighted.")]
        public bool enableSubmeshHighlight = false;

        protected Material stencilOutline;
        protected Renderer[] highlightModels;
        protected string[] copyComponents = new string[] { "UnityEngine.MeshFilter", "UnityEngine.MeshRenderer" };

        /// <summary>
        /// The Initialise method sets up the highlighter for use.
        /// </summary>
        /// <param name="color">Not used.</param>
        /// <param name="affectObject">An optional GameObject to specify which object to apply the highlighting to.</param>
        /// <param name="options">A dictionary array containing the highlighter options:\r     * `&lt;'thickness', float&gt;` - Same as `thickness` inspector parameter.\r     * `&lt;'customOutlineModels', GameObject[]&gt;` - Same as `customOutlineModels` inspector parameter.\r     * `&lt;'customOutlineModelPaths', string[]&gt;` - Same as `customOutlineModelPaths` inspector parameter.</param>
        public override void Initialise(Color? color = null, GameObject affectObject = null, Dictionary<string, object> options = null)
        {
            objectToAffect = (affectObject != null ? affectObject : gameObject);
            usesClonedObject = true;

            if (stencilOutline == null)
            {
                stencilOutline = Instantiate((Material)Resources.Load("OutlineBasic"));
            }
            SetOptions(options);
            ResetHighlighter();
        }

        /// <summary>
        /// The ResetHighlighter method creates the additional model to use as the outline highlighted object.
        /// </summary>
        public override void ResetHighlighter()
        {
            DeleteExistingHighlightModels();
            //First try and use the paths if they have been set
            ResetHighlighterWithCustomModelPaths();
            //If the custom models have been set then use these to override any set paths.
            ResetHighlighterWithCustomModels();
            //if no highlights set then try falling back
            ResetHighlightersWithCurrentGameObject();
        }

        /// <summary>
        /// The Highlight method initiates the outline object to be enabled and display the outline colour.
        /// </summary>
        /// <param name="color">The colour to outline with.</param>
        /// <param name="duration">Not used.</param>
        public override void Highlight(Color? color, float duration = 0f)
        {
            if (highlightModels != null && highlightModels.Length > 0 && stencilOutline != null)
            {
                stencilOutline.SetFloat("_Thickness", thickness);
                stencilOutline.SetColor("_OutlineColor", (Color)color);

                for (int i = 0; i < highlightModels.Length; i++)
                {
                    if (highlightModels[i] != null)
                    {
                        highlightModels[i].gameObject.SetActive(true);
                        highlightModels[i].material = stencilOutline;
                    }
                }
            }
        }

        /// <summary>
        /// The Unhighlight method hides the outline object and removes the outline colour.
        /// </summary>
        /// <param name="color">Not used.</param>
        /// <param name="duration">Not used.</param>
        public override void Unhighlight(Color? color = null, float duration = 0f)
        {
            if (objectToAffect == null)
            {
                return;
            }

            if (highlightModels != null)
            {
                for (int i = 0; i < highlightModels.Length; i++)
                {
                    if (highlightModels[i] != null)
                    {
                        highlightModels[i].gameObject.SetActive(false);
                    }
                }
            }
        }

        protected virtual void OnEnable()
        {
            if (customOutlineModels == null)
            {
                customOutlineModels = new GameObject[0];
            }

            if (customOutlineModelPaths == null)
            {
                customOutlineModelPaths = new string[0];
            }
        }

        protected virtual void OnDestroy()
        {
            if (highlightModels != null)
            {
                for (int i = 0; i < highlightModels.Length; i++)
                {
                    if (highlightModels[i] != null)
                    {
                        Destroy(highlightModels[i]);
                    }
                }
            }
            Destroy(stencilOutline);
        }

        protected virtual void ResetHighlighterWithCustomModels()
        {
            if (customOutlineModels != null && customOutlineModels.Length > 0)
            {
                highlightModels = new Renderer[customOutlineModels.Length];
                for (int i = 0; i < customOutlineModels.Length; i++)
                {
                    highlightModels[i] = CreateHighlightModel(customOutlineModels[i], "");
                }
            }
        }

        protected virtual void ResetHighlighterWithCustomModelPaths()
        {
            if (customOutlineModelPaths != null && customOutlineModelPaths.Length > 0)
            {
                highlightModels = new Renderer[customOutlineModelPaths.Length];
                for (int i = 0; i < customOutlineModelPaths.Length; i++)
                {
                    highlightModels[i] = CreateHighlightModel(null, customOutlineModelPaths[i]);
                }
            }
        }

        protected virtual void ResetHighlightersWithCurrentGameObject()
        {
            if (highlightModels == null || highlightModels.Length == 0)
            {
                highlightModels = new Renderer[1];
                highlightModels[0] = CreateHighlightModel(null, "");
            }
        }

        protected virtual void SetOptions(Dictionary<string, object> options = null)
        {
            float tmpThickness = GetOption<float>(options, "thickness");
            if (tmpThickness > 0f)
            {
                thickness = tmpThickness;
            }

            GameObject[] tmpCustomModels = GetOption<GameObject[]>(options, "customOutlineModels");
            if (tmpCustomModels != null)
            {
                customOutlineModels = tmpCustomModels;
            }

            string[] tmpCustomModelPaths = GetOption<string[]>(options, "customOutlineModelPaths");
            if (tmpCustomModelPaths != null)
            {
                customOutlineModelPaths = tmpCustomModelPaths;
            }
        }

        protected virtual void DeleteExistingHighlightModels()
        {
            VRTK_PlayerObject[] existingHighlighterObjects = objectToAffect.GetComponentsInChildren<VRTK_PlayerObject>(true);
            for (int i = 0; i < existingHighlighterObjects.Length; i++)
            {
                if (existingHighlighterObjects[i].objectType == VRTK_PlayerObject.ObjectTypes.Highlighter)
                {
                    Destroy(existingHighlighterObjects[i].gameObject);
                }
            }
            highlightModels = new Renderer[0];
        }

        protected virtual Renderer CreateHighlightModel(GameObject givenOutlineModel, string givenOutlineModelPath)
        {
            if (givenOutlineModel != null)
            {
                givenOutlineModel = (givenOutlineModel.GetComponent<Renderer>() ? givenOutlineModel : givenOutlineModel.GetComponentInChildren<Renderer>().gameObject);
            }
            else if (givenOutlineModelPath != "")
            {
                Transform getChildModel = objectToAffect.transform.Find(givenOutlineModelPath);
                givenOutlineModel = (getChildModel ? getChildModel.gameObject : null);
            }

            GameObject copyModel = givenOutlineModel;
            if (copyModel == null)
            {
                Renderer copyModelRenderer = objectToAffect.GetComponentInChildren<Renderer>();
                copyModel = (copyModelRenderer != null ? copyModelRenderer.gameObject : null);
            }

            if (copyModel == null)
            { 
                VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "VRTK_OutlineObjectCopyHighlighter", "Renderer", "the same or child", " to add the highlighter to"));
                return null;
            }

            GameObject highlightModel = new GameObject(objectToAffect.name + "_HighlightModel");
            highlightModel.transform.SetParent(copyModel.transform.parent, false);
            highlightModel.transform.localPosition = copyModel.transform.localPosition;
            highlightModel.transform.localRotation = copyModel.transform.localRotation;
            highlightModel.transform.localScale = copyModel.transform.localScale;
            highlightModel.transform.SetParent(objectToAffect.transform);

            Component[] copyModelComponents = copyModel.GetComponents<Component>();
            for (int i = 0; i < copyModelComponents.Length; i++)
            {
                Component copyModelComponent = copyModelComponents[i];
                if (Array.IndexOf(copyComponents, copyModelComponent.GetType().ToString()) >= 0)
                {
                    VRTK_SharedMethods.CloneComponent(copyModelComponent, highlightModel);
                }
            }

            MeshFilter copyMesh = copyModel.GetComponent<MeshFilter>();
            MeshFilter highlightMesh = highlightModel.GetComponent<MeshFilter>();
            Renderer returnHighlightModel = highlightModel.GetComponent<Renderer>();
            if (highlightMesh != null)
            {
                if (enableSubmeshHighlight)
                {
                    HashSet<CombineInstance> combine = new HashSet<CombineInstance>();
                    for (int i = 0; i < copyMesh.mesh.subMeshCount; i++)
                    {
                        CombineInstance ci = new CombineInstance();
                        ci.mesh = copyMesh.mesh;
                        ci.subMeshIndex = i;
                        ci.transform = copyMesh.transform.localToWorldMatrix;
                        combine.Add(ci);
                    }

                    highlightMesh.mesh = new Mesh();
                    highlightMesh.mesh.CombineMeshes(combine.ToArray(), true, false);
                }
                else
                {
                    highlightMesh.mesh = copyMesh.mesh;
                }
                returnHighlightModel.material = stencilOutline;
                returnHighlightModel.shadowCastingMode = copyModel.transform.GetComponent<Renderer>().shadowCastingMode;
            }
            highlightModel.SetActive(false);

            VRTK_PlayerObject.SetPlayerObject(highlightModel, VRTK_PlayerObject.ObjectTypes.Highlighter);

            return returnHighlightModel;
        }
    }
}

UITipsCanvas

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

/// <summary>
/// 指向目标位置
/// </summary>
public class UITipsCanvas : MonoBehaviour {

    public  GameObject player;
    private Vector3 targetPos; 

    void Start () {

        // player = GameObject.FindGameObjectWithTag(Tags.Player);
     
        targetPos = new Vector3(0,transform.position.y,0);
    }
    
    
    void Update () {

        targetPos.x = player.transform.position.x;
        targetPos.z = player.transform.position.z;
        transform.LookAt(targetPos);
        
       
    }
}

PointerManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VRTK;
using VRTK.Highlighters;

/// <summary>
/// 指针管理
/// </summary>
public class PointerManager : MonoBehaviour {

    public Color EnterColor, SetColor;

    private VRTK_Pointer pointer;

    private int musicID;

    private  GameObject go;
    private void Awake()
    {
        pointer = GetComponent<VRTK_Pointer>();
        pointer.DestinationMarkerEnter += Pointer_DestinationMarkerEnter;
        pointer.DestinationMarkerExit += Pointer_DestinationMarkerExit;
        pointer.DestinationMarkerSet += Pointer_DestinationMarkerSet;
    }

    private void OnDestroy()
    {
        pointer.DestinationMarkerEnter -= Pointer_DestinationMarkerEnter;
        pointer.DestinationMarkerExit -= Pointer_DestinationMarkerExit;
        pointer.DestinationMarkerSet -= Pointer_DestinationMarkerSet;
    }


    private void Pointer_DestinationMarkerSet(object sender, DestinationMarkerEventArgs e)
    {
        HighLight(e.target,SetColor);
        switch (e.target.name)
        {
            case "Mercury":
                musicID = 1;
                break;
            case "Venus":
                musicID = 2;
                break;
            case "Earth":
                musicID = 3;
                break;
            case "Mars":
                musicID = 4;
                break;
            case "Jupiter":
                musicID = 5;
                break;
            case "Saturn":
                musicID = 6;
                break;
            case "Uranus":
                musicID = 7;
                break;
            case "Neptune":
                musicID = 8;
                break;
            case "Sun":
                musicID = 9;
                break;
            default:
                break;
        }
        AudioManager.Instance.PlaySound(musicID);
    }

    private void Pointer_DestinationMarkerExit(object sender, DestinationMarkerEventArgs e)
    {
        //GameObject go = e.target.transform.GetChild(0).gameObject;
        try
        {
            go = e.target.transform.Find("UITipsCanvas").gameObject;
            go.SetActive(false);
        }
        catch (System.Exception)
        {
            Debug.Log("不存在");
        } 
        HighLight(e.target,Color.clear);
      
    }

    private void Pointer_DestinationMarkerEnter(object sender, DestinationMarkerEventArgs e)
    {
        // GameObject go = e.target.transform.GetChild(0).gameObject;
        try
        {
            go = e.target.transform.Find("UITipsCanvas").gameObject;
            go.SetActive(true);
        }
        catch (System.Exception)
        {
            Debug.Log("不存在");
        }
       
        HighLight(e.target,EnterColor);

    }

    /// <summary>
    /// 高亮
    /// </summary>
    /// <param name="target"></param>
    /// <param name="color"></param>
    private void HighLight(Transform target,Color color) {

        VRTK_BaseHighlighter highlighter = (target!=null?target.GetComponent<VRTK_BaseHighlighter>():null);
        if (highlighter!=null)
        {
            highlighter.Initialise();
            if (color!=Color.clear)
            {
                highlighter.Highlight(color);
            }
            else
            {
                highlighter.Unhighlight();
            }

        }

    }

}

AudioManager

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using System;

public class AudioManager : MonoBehaviour
{
    private static AudioManager instance;

    public static AudioManager Instance
    {
        get
        {
            return instance;
        }
    }

    private Dictionary<int, string> audioPathDict;      // 存放音频文件路径

    private AudioSource musicAudioSource;

    private List<AudioSource> unusedSoundAudioSourceList;   // 存放可以使用的音频组件

    private List<AudioSource> usedSoundAudioSourceList;     // 存放正在使用的音频组件

    private Dictionary<int, AudioClip> audioClipDict;       // 缓存音频文件

    private float musicVolume = 1;

    private float soundVolume = 1;

    private string musicVolumePrefs = "MusicVolume";

    private string soundVolumePrefs = "SoundVolume";

    private int poolCount = 3;         // 对象池数量

    void Awake()
    {
        DontDestroyOnLoad(this.gameObject);
        instance = this;

        audioPathDict = new Dictionary<int, string>()       // 这里设置音频文件路径。需要修改。 TODO
        {

            { 1, "Audios/水星"},
            { 2, "Audios/金星"},
            { 3,"Audios/地球"},
            { 4,"Audios/火星"},
            { 5,"Audios/木星"},
            { 6,"Audios/土星"},
            { 7,"Audios/天王星"},
            { 8,"Audios/海王星"},
        };

        musicAudioSource = gameObject.AddComponent<AudioSource>();
        musicAudioSource.spatialBlend = 1;//设置3D音效
        musicAudioSource.maxDistance = 150; //设置3d距离
        unusedSoundAudioSourceList = new List<AudioSource>();
        usedSoundAudioSourceList = new List<AudioSource>();
        audioClipDict = new Dictionary<int, AudioClip>();
    }

    void Start()
    {
        // 从本地缓存读取声音音量
        if (PlayerPrefs.HasKey(musicVolumePrefs))
        {
            musicVolume = PlayerPrefs.GetFloat(musicVolumePrefs);
        }
        if (PlayerPrefs.HasKey(soundVolumePrefs))
        {
            musicVolume = PlayerPrefs.GetFloat(soundVolumePrefs);
        }
    }

    /// <summary>
    /// 播放背景音乐
    /// </summary>
    /// <param name="id"></param>
    /// <param name="loop"></param>
    public void PlayMusic(int id, bool loop = true)
    {
        // 通过Tween将声音淡入淡出
        DOTween.To(() => musicAudioSource.volume, value => musicAudioSource.volume = value, 0, 0.5f).OnComplete(() =>
        {
            musicAudioSource.clip = GetAudioClip(id);
            musicAudioSource.clip.LoadAudioData();
            musicAudioSource.loop = loop;
            musicAudioSource.volume = musicVolume;
            musicAudioSource.Play();
            DOTween.To(() => musicAudioSource.volume, value => musicAudioSource.volume = value, musicVolume, 0.5f);
        });
    }

    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="id"></param>
    public void PlaySound(int id, Action action = null)
    {
        
        if (unusedSoundAudioSourceList.Count != 0)
        {
            AudioSource audioSource = UnusedToUsed();
            audioSource.clip = GetAudioClip(id);
            audioSource.clip.LoadAudioData();
            audioSource.Play();

            StartCoroutine(WaitPlayEnd(audioSource, action));
        }
        else
        {
            AddAudioSource();

            AudioSource audioSource = UnusedToUsed();
            audioSource.clip = GetAudioClip(id);
            audioSource.clip.LoadAudioData();
            audioSource.volume = soundVolume;
            audioSource.loop = false;
            audioSource.Play();

            StartCoroutine(WaitPlayEnd(audioSource, action));
        }
    }

    /// <summary>
    /// 播放3d音效
    /// </summary>
    /// <param name="id"></param>
    /// <param name="position"></param>
    public void Play3dSound(int id, Vector3 position)
    {
        AudioClip ac = GetAudioClip(id);
        AudioSource.PlayClipAtPoint(ac, position);
    }

    /// <summary>
    /// 当播放音效结束后,将其移至未使用集合
    /// </summary>
    /// <param name="audioSource"></param>
    /// <returns></returns>
    IEnumerator WaitPlayEnd(AudioSource audioSource, Action action)
    {
        yield return new WaitUntil(() => { return !audioSource.isPlaying; });
        UsedToUnused(audioSource);
        if (action != null)
        {
            action();
        }
    }

    /// <summary>
    /// 获取音频文件,获取后会缓存一份
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private AudioClip GetAudioClip(int id)
    {
        if (!audioClipDict.ContainsKey(id))
        {
            if (!audioPathDict.ContainsKey(id))
                return null;
            AudioClip ac = Resources.Load(audioPathDict[id]) as AudioClip;
            audioClipDict.Add(id, ac);
        }
        return audioClipDict[id];
    }

    /// <summary>
    /// 添加音频组件
    /// </summary>
    /// <returns></returns>
    private AudioSource AddAudioSource()
    {
        if (unusedSoundAudioSourceList.Count != 0)
        {
            return UnusedToUsed();
        }
        else
        { 

            AudioSource audioSource ;
            if (gameObject.AddComponent<AudioSource>()==null)
            {
                audioSource = gameObject.AddComponent<AudioSource>();
            }
            else
            {
                audioSource = gameObject.GetComponent<AudioSource>();
            }
            unusedSoundAudioSourceList.Add(audioSource);
            return audioSource;
        }
    }

    /// <summary>
    /// 将未使用的音频组件移至已使用集合里
    /// </summary>
    /// <returns></returns>
    private AudioSource UnusedToUsed()
    {
        AudioSource audioSource = unusedSoundAudioSourceList[0];
        unusedSoundAudioSourceList.RemoveAt(0);
        usedSoundAudioSourceList.Add(audioSource);
        return audioSource;
    }

    /// <summary>
    /// 将使用完的音频组件移至未使用集合里
    /// </summary>
    /// <param name="audioSource"></param>
    private void UsedToUnused(AudioSource audioSource)
    {
        if (usedSoundAudioSourceList.Contains(audioSource))
        {
            usedSoundAudioSourceList.Remove(audioSource);
        }
        if (unusedSoundAudioSourceList.Count >= poolCount)
        {
            Destroy(audioSource);
        }
        else if (audioSource != null && !unusedSoundAudioSourceList.Contains(audioSource))
        {
            unusedSoundAudioSourceList.Add(audioSource);
        }
    }

    /// <summary>
    /// 修改背景音乐音量
    /// </summary>
    /// <param name="volume"></param>
    public void ChangeMusicVolume(float volume)
    {
        musicVolume = volume;
        musicAudioSource.volume = volume;

        PlayerPrefs.SetFloat(musicVolumePrefs, volume);
    }

    /// <summary>
    /// 修改音效音量
    /// </summary>
    /// <param name="volume"></param>
    public void ChangeSoundVolume(float volume)
    {
        soundVolume = volume;
        for (int i = 0; i < unusedSoundAudioSourceList.Count; i++)
        {
            unusedSoundAudioSourceList[i].volume = volume;
        }
        for (int i = 0; i < usedSoundAudioSourceList.Count; i++)
        {
            usedSoundAudioSourceList[i].volume = volume;
        }

        PlayerPrefs.SetFloat(soundVolumePrefs, volume);
    }

    /// <summary>
    /// 是否3d音效
    /// </summary>
    /// <param name="is3d"></param>
    public void Is3DSounds(bool is3d)
    {
        if (is3d)
            musicAudioSource.spatialBlend = 1;//设置3D音效  
        else
            musicAudioSource.spatialBlend = 0;//设置3D音效  

    }

    /// <summary>
    /// 设置3d音效距离
    /// </summary>
    /// <param name="distance"></param>
    public void Set3DDistance(float distance)
    {

        musicAudioSource.maxDistance = distance; //设置3d音效距离
    }

    /// <summary>
    /// 设置3D位置信息
    /// </summary>
    /// <param name="pos"></param>
    public void SetAudioPos(Vector3 pos)
    {
        transform.position = pos;
    }

    /// <summary>
    /// 播放速度
    /// </summary>
    /// <param name="isFast"></param>
    /// <param name="speed"></param>
    public void PlaySpeed(bool isFast, float speed)
    {
        if (isFast)
        {
            musicAudioSource.pitch = speed;

        }
        else
        {
            musicAudioSource.pitch = 1;
        }

    }

    /// <summary>
    /// 停止播放
    /// </summary>
    public void StopSound() {

        musicAudioSource.Stop();
    }
}

三、主要效果图