VR-Sample:4.游戏菜单

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


游戏菜单.png

</p>

一、实现功能

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

二、步骤

1. UI的制作

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


游戏菜单.png

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

1.1 BackGround

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


BackGround.png

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


MenuInfo.png

完成之后呈现如下图:


front.png

behind.png

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

1.2 Menu

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


Menu.png

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


ItemFly.png

FlyDescription.png

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


Mesh Collider.png

Shooter360中的设置如下图:

ItemShooter360.png

ShooterDescription.png

完成之后,呈现如下图:


Menu.png
1.3 Selector

Selector的设置类似,如下图:


Selector.png

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

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 这两个脚本,设置如下:


ItemFly.png

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

2.2 界面动画播放

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


Material.png

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上,设置如下:

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上,设置如下:


Selector.png

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


SelectorMove.png
2.4准心和选择条的填充以及进入相应场景功能
  • 进度条的填充
      这个功能使用到是SelectionSlider脚本,这个脚本用来控制Slider的填充,设置如下:


    SelectionSlider.png

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


填充方式.png
  • 准心填充以及进入下一个场景
      这里使用到了MenuButton脚本,里面控制了准心背景的显示,以及进入下一个场景两个功能,脚本注释如下:
public class MenuButton : MonoBehaviour
    {
        //这个事件在当选中的 MenuButton进度条完成后执行
        public event Action<MenuButton> 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上,设置如下:


MenuButton.png

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

三.注意事项

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

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


OnButtonSelected_Use.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 大学时代,在图书馆曾看过国斯宾塞.约翰逊所著《谁动了我的奶酪》,年轻气盛的我当时对这本书并没有什么感觉,现在记忆中...
    宁博Villa阅读 760评论 2 5
  • 每个人心底总会有个人 当你与他目光交错 就像是触到花火 他的眼睛里满是深邃 旋转着温暖的银河 我总是不禁地沉溺 深...
    STARLIGHTSMoon阅读 82评论 0 0
  • “当局者迷 旁观者清”,亘古不变的真理! 倾听小伙伴讲述她的恋爱故事,正处在热恋中的她变得越来越漂亮,也变得越来越...
    福贵牌Romeo阅读 477评论 0 0