VR-Sample:4.游戏菜单


  本篇完成游戏菜单界面,场景中呈现所有的游戏选择界面,激活相应的游戏界面进入游戏场景,这里面使用到了VRStandardAssets.Menu中的一些脚本。下面用到的时候会写出注释,场景如下图:


VR-Sample:4.游戏菜单_第1张图片
游戏菜单.png

一、实现功能

  • 游戏选择界面的动画播放
      当视线准心选择界面的时候,界面会向玩家弹出一段距离,并播放该游戏的介绍动画,视线移开时,停止播放,界面回到原来位置。
  • 选择条的跟随,填充和场景的进入
      选择条会动态的移动到当前准心的游戏界面上,并且在进入过程中会进行填充,填充完毕后开始相应的游戏。

二、步骤

1. UI的制作

游戏菜单的UI分为3块:BackGround,Menu,Selector。


VR-Sample:4.游戏菜单_第2张图片
游戏菜单.png

下面开始一步一步制作,首先是BackGround,这里需要注意的是:3D UI的呈现是通过MeshFilter和MeshRenderer这两个组件,MeshFilter加载Mesh,MeshRenderer呈现Mesh;

1.1 BackGround

首先创建一个空对象,命名为BackGround,作为所有背景元素的父对象存在:


VR-Sample:4.游戏菜单_第3张图片
BackGround.png

MenuBg和上一篇中一样(注意环境光的设置),这里就不在叙述直接进入MenuInfo步骤,创建一个空对象作为BackGround的子对象,同时加入MeshFilter和MeshRenderer这两个组件,同时添加上如下图所示的Mesh和Materials:


VR-Sample:4.游戏菜单_第4张图片
MenuInfo.png

完成之后呈现如下图:


VR-Sample:4.游戏菜单_第5张图片
front.png

VR-Sample:4.游戏菜单_第6张图片
behind.png

MenuLogo在前面,MenuElements在后面,旋转一下就可看到了。

1.2 Menu

同样的,在Menu中也包含了两个子元素:Fly和Shooter360,按照如下图的层级关系建立空对象:


VR-Sample:4.游戏菜单_第7张图片
Menu.png

完成后,在ItemFly和FlyDescription上分别添加MeshFilter和MeshRenderer组件,如下图:


VR-Sample:4.游戏菜单_第8张图片
ItemFly.png

VR-Sample:4.游戏菜单_第9张图片
FlyDescription.png

这里需要注意的是,这个ItemFly是一个可交互项,所以添加一个Mesh Collider组件来响应射线的碰撞检测:


VR-Sample:4.游戏菜单_第10张图片
Mesh Collider.png

Shooter360中的设置如下图:

VR-Sample:4.游戏菜单_第11张图片
ItemShooter360.png

VR-Sample:4.游戏菜单_第12张图片
ShooterDescription.png

完成之后,呈现如下图:


VR-Sample:4.游戏菜单_第13张图片
Menu.png
1.3 Selector

Selector的设置类似,如下图:


VR-Sample:4.游戏菜单_第14张图片
Selector.png

完成设置后,游戏菜单的UI部分就完成,呈现如下图:

VR-Sample:4.游戏菜单_第15张图片
Completed.png
2.界面功能实现

在VR-Sample中所有的菜单功能在VRStandardAssets.Menu这个命名空间下。

2.1 界面的弹出功能

当准心移动到游戏界面时,界面会弹出一段距离,提醒玩家当前选择的是该游戏,使用到的是MenuItemPopout 脚本,代码的注释如下:

  public class MenuItemPopout : MonoBehaviour
    {
        //控制弹出的Transform组件,用来改变位置
        [SerializeField] private Transform m_Transform;         
        //弹出的交互项
        [SerializeField] private VRInteractiveItem m_Item;      
        //弹出的速度
        [SerializeField] private float m_PopSpeed = 8f;         
        //弹出的距离
        [SerializeField] private float m_PopDistance = 0.5f;    

        //交互项的原始位置
        private Vector3 m_StartPosition;                       
        //交互项需要弹出到的目标位置
        private Vector3 m_PoppedPosition;                       
        //当前需要移动到的位置(被选中时为目标位置m_PoppedPosition,未被选中时为原始位置m_StartPosition)
        private Vector3 m_TargetPosition;                       


        private void Start ()
        {
            // 开始运行时保存交互项的初始位置
            m_StartPosition = m_Transform.position;

            // 保存目标位置
            m_PoppedPosition = m_Transform.position - m_Transform.forward * m_PopDistance;
        }


        private void Update ()
        {
            // 确定当前需要移动到的位置,被选中为m_PoppedPosition,没有被选中为m_StartPosition
            m_TargetPosition = m_Item.IsOver ? m_PoppedPosition : m_StartPosition;

            // MoveTowards方法移动位置
            m_Transform.position = Vector3.MoveTowards(m_Transform.position, m_TargetPosition, m_PopSpeed * Time.deltaTime);
        }
    }

完成后,在ItemFly上挂载VRInteractiveItem和MenuItemPopout 这两个脚本,设置如下:


VR-Sample:4.游戏菜单_第16张图片
ItemFly.png

同样,ItemShooter360设置和ItemFly一致,设置完成后,当准心移动到Fly界面时,界面会弹出一段距离。

2.2 界面动画播放

完成弹出功能后,继续进行动画播放的功能实现,大致思路为:将一组连续的图片纹理快速的替换到MeshRenderer的Material中,达到动画播放的效果:


VR-Sample:4.游戏菜单_第17张图片
Material.png

VR-Sample:4.游戏菜单_第18张图片
material.mainTexture.png

使用到的脚本为MenuAnimator ,注释如下:

    public class MenuAnimator : MonoBehaviour
    {
        //每秒图片纹理改变的速度
        [SerializeField] private int m_FrameRate = 30;                 
        //需要做呈现的MeshRenderer
        [SerializeField] private MeshRenderer m_ScreenMesh;             
        //当前的交互项
        [SerializeField] private VRInteractiveItem m_VRInteractiveItem; 
        //保存图片的数组
        [SerializeField] private Texture[] m_AnimTextures;              

        //协程方法的等待间隔
        private WaitForSeconds m_FrameRateWait;                        
        //当前图片纹理的序号
        private int m_CurrentTextureIndex;                              
        //是否播放的标志
        private bool m_Playing;                                         


        private void Awake ()
        {
            // 新建一个WaitForSeconds对象
            m_FrameRateWait = new WaitForSeconds (1f / m_FrameRate);
        }


        private void OnEnable ()
        {
            //订阅交互项的OnOver和OnOut事件
            m_VRInteractiveItem.OnOver += HandleOver;
            m_VRInteractiveItem.OnOut += HandleOut;
        }

        //取消订阅交互项的OnOver和OnOut事件
        private void OnDisable ()
        {
            m_VRInteractiveItem.OnOver -= HandleOver;
            m_VRInteractiveItem.OnOut -= HandleOut;
        }
        private void HandleOver ()
        {
            m_Playing = true;
            StartCoroutine (PlayTextures ());
        }
        private void HandleOut ()
        {
            m_Playing = false;
        }
        private IEnumerator PlayTextures ()
        {
            // 当被准心选中时,这是一个死循环,动画会一直播放
            while (m_Playing)
            {
                // 修改m_ScreenMesh材质中的图片纹理为第m_CurrentTextureIndex个
                m_ScreenMesh.material.mainTexture = m_AnimTextures[m_CurrentTextureIndex];

                // m_CurrentTextureIndex自增,当到达最后一个时又从0开始
                m_CurrentTextureIndex = (m_CurrentTextureIndex + 1) % m_AnimTextures.Length;

                // 等待一个m_FrameRateWait在执行
                yield return m_FrameRateWait;
            }
        }
    }

完成后,将脚本挂载到ItemFly上,设置如下:

VR-Sample:4.游戏菜单_第19张图片
ItemFly.png

在ItemShooter360上也做同样的设置,完成后,当准心移动到Fly界面上时,播放相应的动画。

2.3选择条跟随移动和弹出

当准心移动到某个游戏界面时,界面下方的选择条会跟随移动,弹出提示当前的选择界面,这个功能使用到的是MenuSelectorMover脚本,值得一提的是,这里是通过父对象来控制旋转,子对象(即当前的交互项来控制移动),注释如下:

    public class MenuSelectorMover : MonoBehaviour
    {
        //弹出的速度
        [SerializeField] private float m_PopSpeed = 8f;         
        //弹出的距离
        [SerializeField] private float m_PopDistance = 0.5f;    
        //跟随移动的速度
        [SerializeField] private float m_MoveSpeed = 7f;        
        //父对象的Transform,用来控制Rotation移动
        [SerializeField] private Transform m_ParentTransform;   
        //子对象的Transform,用来控制自身的弹出
        [SerializeField] private Transform m_ChildTransform;    
        //场景中的所有界面交互项数组
        [SerializeField] private VRInteractiveItem[] m_Items;   

        //旋转到的目标位置
        private Quaternion m_TargetRotation;                    
        //弹出的初始位置
        private Vector3 m_StartPosition;                        
        //弹出的目标位置
        private Vector3 m_PoppedPosition;                       
        //当前需要到达的位置
        private Vector3 m_TargetPosition;                       


        void Awake ()
        {
            // 保存初始位置
            m_StartPosition = m_ChildTransform.localPosition;

            // 保存需要弹出到的位置
            m_PoppedPosition = m_ChildTransform.localPosition - Vector3.forward * m_PopDistance;
        }

        
        void Update ()
        {
            // 选择条默认位置为初始位置
            m_TargetPosition = m_StartPosition;

            // 遍历交互项数组,如果有交互项被选中,则将m_TargetPosition位置修改,没有则不变
            for (int i = 0; i < m_Items.Length; i++)
            {
                //判断是否有交互项被选中
                if (!m_Items[i].IsOver)
                    continue;

                //设置目标位置的Rotation
                m_TargetRotation = m_Items[i].transform.rotation;
                //设置目标位置的Postion为m_PoppedPosition
                m_TargetPosition = m_PoppedPosition;
                break;
            }

            // 使用Vector3.MoveTowards方法控制跟随移动
            m_ChildTransform.localPosition = Vector3.MoveTowards (m_ChildTransform.localPosition, m_TargetPosition,
                m_PopSpeed * Time.deltaTime);

            //使用Quaternion.Slerp方法控制旋转
            m_ParentTransform.rotation = Quaternion.Slerp(m_ParentTransform.rotation, m_TargetRotation, m_MoveSpeed * Time.deltaTime);
        }

完成后,挂载到Selector上,设置如下:


VR-Sample:4.游戏菜单_第20张图片
Selector.png

运行后,选择条就可以跟随当前的交互项而移动了。


VR-Sample:4.游戏菜单_第21张图片
SelectorMove.png
2.4准心和选择条的填充以及进入相应场景功能
  • 进度条的填充
      这个功能使用到是SelectionSlider脚本,这个脚本用来控制Slider的填充,设置如下:


    VR-Sample:4.游戏菜单_第22张图片
    SelectionSlider.png

这里需要注意的是,这个脚本可以做2D或者3D的填充,2D使用Slider字段,3D使用Renderer字段:


VR-Sample:4.游戏菜单_第23张图片
填充方式.png
  • 准心填充以及进入下一个场景
      这里使用到了MenuButton脚本,里面控制了准心背景的显示,以及进入下一个场景两个功能,脚本注释如下:
public class MenuButton : MonoBehaviour
    {
        //这个事件在当选中的 MenuButton进度条完成后执行
        public event Action OnButtonSelected;                   

        //下一个场景的名字
        [SerializeField] private string m_SceneToLoad;                      
        //摄像机的淡出脚本,进入下一个场景时需要进行摄像机的淡出动作
        [SerializeField] private VRCameraFade m_CameraFade;                
        //准心的背景控制脚本,需要控制背景的显示和隐藏以及填充完毕时订阅相应的事件
        [SerializeField] private SelectionRadial m_SelectionRadial;         
        //当前的交互项
        [SerializeField] private VRInteractiveItem m_InteractiveItem;       

        //准心是否移入
        private bool m_GazeOver;                                            

        //事件的订阅
        private void OnEnable ()
        {
            m_InteractiveItem.OnOver += HandleOver;
            m_InteractiveItem.OnOut += HandleOut;
            m_SelectionRadial.OnSelectionComplete += HandleSelectionComplete;
        }

        //取消事件订阅
        private void OnDisable ()
        {
            m_InteractiveItem.OnOver -= HandleOver;
            m_InteractiveItem.OnOut -= HandleOut;
            m_SelectionRadial.OnSelectionComplete -= HandleSelectionComplete;
        }
        
        //准心移入时执行的方法
        private void HandleOver()
        {
            //准心背景显示
            m_SelectionRadial.Show();

            m_GazeOver = true;
        }

        //准心移出时执行的方法
        private void HandleOut()
        {
            // 准心背景隐藏
            m_SelectionRadial.Hide();

            m_GazeOver = false;
        }

        //当准心背景填充完毕时执行的方法
        private void HandleSelectionComplete()
        {
            //准心在当前交互项时
            if(m_GazeOver)
                StartCoroutine (ActivateButton());
        }

        private IEnumerator ActivateButton()
        {
            // 当摄像机正在渐入时,不执行
            if (m_CameraFade.IsFading)
                yield break;

            //当事件被订阅时执行
            if (OnButtonSelected != null)
                OnButtonSelected(this);

            // 开始摄像机渐出的动作.
            yield return StartCoroutine(m_CameraFade.BeginFadeOut(true));

            // 载入下一个场景
            SceneManager.LoadScene(m_SceneToLoad, LoadSceneMode.Single);
        }
    }

完成后,挂载在ItemFly上,设置如下:


VR-Sample:4.游戏菜单_第24张图片
MenuButton.png

同样的,在Shoter360上配置一样,这里完成后,就可以进行运行测试啦。

三.注意事项

1.MenuButton脚本中OnButtonSelected事件使用:
OnButtonSelected.png

这个是一个Action事件,添加了一个MenuButton参数,在使用的时候把当前脚本this作为参数传递:


OnButtonSelected_Use.png

这样当其他地方订阅这个事件的时候,在Reticle填充完毕后,可以对这个MenuButtion进行一些操作,当然也可以做其他的操作不管这个参数。

你可能感兴趣的:(VR-Sample:4.游戏菜单)