VR开发--虚拟与现实游戏(VR-狩猎)

作者: 元宇宙协会 | 来源:发表于2016-10-04 16:27 被阅读689次

    国庆期间本来是想找份工作的,结果目前没有合适的。只好闭门造车。。。感慨世事万千,生命的神奇。废话不多说,三句就够了!

    00.png
    1、前期准备

    1、PC平台
    2、资源(UI素材,粒子特效,动画等)
    3、导入SteamVR
    4、那个运行HTC Vive设备最少970显卡

    01.png

    注意:全部选择

    2、导入3D视角
    2.png
    3、导入模型资源

    需要那个手柄控制,就放置在那个手柄下

    04.png
    4、基于设备调整好模型与手柄之间的角度、距离
    10.png
    5、针对箭头,挂载脚本

    设置箭头的位置和控制箭头的父物体,脚本在父物体挂载

    05.png
    6、设置弓与箭的触发器
    06.png 07.png
    7、实例化一个箭头
    08.png

    箭头与弓是分离的,所以在手柄控制器中,放置在string里面来达到收纳箭头,控制箭头的位置信息

    10.png 12.png
    8、拉动弓箭

    8.1箭头控制器应该拿到弓玄的起始位置

    13.png

    8.2弓箭的起始位置与拉动位置

    14.png 15.png
    9、箭的发射

    箭头所在的脚本:

    16.png

    箭头控制器里面的方法:

    17.png 18.png

    射箭:

    19.png

    上面就是开发一款虚拟与现实最简单的应用(国外的开发牛人提供的素材)
    箭头控制器源码:

    using UnityEngine;
    using System.Collections;
    using System;
    
    public class ArrowsManager : MonoBehaviour {
        public float dir; 
        // 实例化对象
        public static ArrowsManager instance;
        void Awake()
        {
            instance = this;
        }
    
        // 过渡游戏对象
        private GameObject curArrow;
        // 实际的箭头
        public GameObject arrowPf;
        // 获得VR设备(因为箭头要在控制手柄上,所以必须要有手柄对象)
        public SteamVR_TrackedObject trackObj;
    
        // 拥有箭头位置的对象,也就是箭头在手柄内部位置
        public GameObject stringAttachPoint;
        // 开始点
        public GameObject arrowStartPoint;
        // 弓玄的起始位置
        public GameObject StringStartPoint;
        // 判断是否触发
        private bool isAttached;
    
        void Update () {
            AttachArrow(); 
            PullString(); // 判断拉弓
        }
    
        // 射箭  
        private void Fire()
        {
            curArrow.transform.parent = null;
            // 拿到当前箭头的刚体组件
            var r = curArrow.GetComponent<Rigidbody>();
            r.useGravity = true; // 使用重力
            r.velocity = curArrow.transform.forward * 50f * dir; //设置刚体的速度
    
            // 将弓玄string还原
            stringAttachPoint.transform.position = StringStartPoint.transform.position;
    
            curArrow = null; // 射出去了,当前箭头就为空
            isAttached = false; // 射出去后,就不会在触发了。
        }
    
        // 箭头的实时位置
        void AttachArrow()
        {
            if (curArrow == null)
            {  // 实例化箭头
                curArrow = Instantiate(arrowPf);
                // 设置箭头的父控件
                arrowPf.transform.parent = trackObj.transform;
                // 设置箭头的地方坐标
                curArrow.transform.localPosition = new Vector3(0, 0, 0.256f);
                // 设置角度
                // Quaternion.identity就是指Quaternion(0,0,0,0),就是每旋转前的初始角度,是一个确切的值,
                // 而transform.rotation是指本物体的角度,是一个属性变量
                curArrow.transform.localRotation = Quaternion.identity;
            }
        }
        
        /*
         *触发器触发后调整箭头位置
        */
        public void AttachBowToArrow()
        {
            // 当前箭头的父控件 = 手柄的位置
            curArrow.transform.parent = stringAttachPoint.transform;
            // 当前箭头的本地坐标就是开始箭头的本地坐标(开始箭头的坐标通过赋值对象的坐标来获取)
            curArrow.transform.localPosition = arrowStartPoint.transform.localPosition;
            // 当前箭头的旋转 = 开始箭头的旋转
            curArrow.transform.rotation = arrowStartPoint.transform.rotation;
            isAttached = true; // 标志位,触发了,其实也就调用了拉动弓玄方法
        }
    
        /*
         *拉动弓玄
        */
        public void PullString()
        {
            if (isAttached) // 如果触发,再调整箭头的位置
            {
                    // InverseTransformPoint:变换位置从自身坐标到世界坐标(弓玄的本地坐标转换成世界坐标的X(就是拉动玄的长度))
                    // 获得转换后的vector的X值
                   dir = StringStartPoint.transform.InverseTransformPoint(trackObj.transform.position).x;
                   print(StringStartPoint.transform.InverseTransformPoint(trackObj.transform.position));
                // 拿到初始弓玄与手柄设备的差值 
                // float dis = (StringStartPoint.transform.position - trackObj.transform.position).magnitude;
                // 箭头的实际位置 = 起始位置+上面的差值
                if (dir < 0)
                {
                    dir = 0;
                }
                dir = dir > 0.4f ? 0.4f : dir;
                stringAttachPoint.transform.localPosition = StringStartPoint.transform.localPosition + new Vector3(dir, 0, 0);
    
                    // 获得输入的VR手柄设备
                    var device = SteamVR_Controller.Input((int)ArrowsManager.instance.trackObj.index);
    
                    // 如果扣动扳机(如果处于攻击),发射弓箭
                    if (device.GetTouch(SteamVR_Controller.ButtonMask.Trigger))
                    {
                        Fire(); // 开火
                    }
            }
        }
    }
    

    箭头挂载的脚本:

    using UnityEngine;
    using System.Collections;
    using System;
    
    public class Arrows : MonoBehaviour {
        private bool isFire;
        private bool isAttached;
    
        void Update () {
            if (isFire)
            {   //当前的朝向  当前的位置+当前刚体的速率
                // LookAt: 朝向,是一个相对坐标
                transform.LookAt(transform.position + transform.GetComponent<Rigidbody>().velocity);
            }
        }
        // 触发器(API)
        void OnTriggerEnter(Collider c)
        {
            AttackArrow();
            AttackEnemy(c);
        }
        // 根据传入的碰撞器标签,来攻击怪物
        private void AttackEnemy(Collider c)
        {
            if (c.tag == "Enemy")
            {   // 拿到碰撞器所在物体的《怪物》脚本执行TakeDamage方法
                c.gameObject.GetComponent<Enmy>().TakeDamage();
            }
        }
    
        public void Fire()
        {
            isFire = true;
        }
         // 攻击
        public void AttackArrow()
        {
           // 获得输入的VR手柄设备
           var device = SteamVR_Controller.Input((int)ArrowsManager.instance.trackObj.index);
    
            // 如果扣动扳机(如果处于攻击)
            if (isAttached == false && device.GetTouch(SteamVR_Controller.ButtonMask.Trigger)) 
            {
                // 拿到Arrowsmanager,调用箭头的位置
                 ArrowsManager.instance.AttachBowToArrow();
                 isAttached = true;
            }   
        }
    }
    

    怪物生成脚本:

    using UnityEngine;
    using System.Collections;
    
    public class EnemySpawner : MonoBehaviour {
    
        // 起始路点
        public PathNode m_startNode;
    
        // 保存所有的从XML读取的数据
        ArrayList m_enemyList;
    
        // 存储敌人出场顺序
        public TextAsset xmldata;
    
        // 出场敌人的序列号
        int m_index = 0;
    
        // 距离下一个敌人的出场时间
        float m_timer = 0;
    
        int liveEnemy;
    
        
        void Start () {
            ReadXML();
    
            // 获取初始敌人
            SpawnData date = (SpawnData)m_enemyList[m_index];
            m_timer = date.wait;
        }
        
        
        void Update () {
            SpawnEnemy();
        }
    
        // 每个一定时间生成一个敌人
        void SpawnEnemy()
        {
            if (m_index >= m_enemyList.Count)
            {
                return;
            }
            // 更新时间,等待下一个敌人
            m_timer -= Time.deltaTime;
            if (m_timer > 0)
            {
                return;
            }
            // 获取下一个敌人的数据
            SpawnData data = (SpawnData)m_enemyList[m_index];
            // 如果下一个敌人是下一波,需要等待前一波敌人全部销毁
            if (GameManager.Instance.wave < data.wave)
            {
                if (liveEnemy > 0)
                {
                    return;
                }
                else
                {
                    GameManager.Instance.wave = data.wave; // 更新wave数值
                }
            }
            m_index++;
            if (m_index < m_enemyList.Count)
            {
                m_timer = ((SpawnData)m_enemyList[m_index]).wait;// 更新等待的时间
            }
            // 读取敌人的模型
            GameObject enemymodel = Resources.Load<GameObject>(data.enemyname);
           // Debug.Log("  调试    "+m_startNode.transform.position);
            // 实例化敌人的模型,并转向第一个路点
            Vector3 dir = m_startNode.transform.position - this.transform.position;
            // 预设物,位置,旋转角度
            GameObject enmeyObj = (GameObject)Instantiate(enemymodel,this.transform.position, Quaternion.LookRotation(dir));
    
            // 添加Enemy
            Enmy eney = enmeyObj.AddComponent<Enmy>();
           
              // 设置敌人出发点
            eney.curNode = m_startNode;
            Debug.Log(m_startNode.transform.position+" 设置敌人出发点 ");
            // 根据data.level设置敌人数值,本示例只是简单的根据波数增加敌人的生命
            eney.m_life = data.level * 3;
            eney.m_maxlife = data.level * 3;
    
            // 更新存活敌人数量
            liveEnemy++;
            // 为敌人指定死亡动作,当敌人死亡回调减少敌人数量
            OnEnmyDeath(eney, (Enmy e) =>
             {
                 liveEnemy--;
             });
    
        }
        // 定义了动作的函数
        void OnEnmyDeath(Enmy eney, System.Action<Enmy> onDeath)
        {
            eney.onDeath = onDeath;
        }
    
        void OnDrawGizmos()
        {
            Gizmos.DrawIcon(transform.position, "spawner.tif");
        }
        void ReadXML()
        {
            m_enemyList = new ArrayList();
            XMLParser xmlparse = new XMLParser();
            XMLNode node = xmlparse.Parse(xmldata.text);
            // 取得XML数据  = 传入XML文件路径
            XMLNodeList list = node.GetNodeList("ROOT>0>table");
            for (int i = 0; i < list.Count; i++)
            {
                string wave = node.GetValue("ROOT>0>table>" + i + ">@wave");
                string enemyname = node.GetValue("ROOT>0>table>" + i + ">@enemyname");
                string level = node.GetValue("ROOT>0>table>" + i + ">@level");
                string wait = node.GetValue("ROOT>0>table>" + i + ">@wait");
    
                SpawnData data = new SpawnData();
                data.wave = int.Parse(wave);
                data.enemyname = enemyname;
                data.level = int.Parse(level);
                data.wait = float.Parse(wait);
    
                m_enemyList.Add(data);
            }  
        }
        // xml数据
        public class SpawnData
        {   // 波数
            public int wave = 1;
            public string enemyname = "";
            public int level = 1;
            public float wait = 1.0f; 
        }
    }
    
    using UnityEngine;
    using System.Collections;
    using System;
    
    public class Enmy : MonoBehaviour {
    
        public PathNode curNode; // 怪物的起始点
        public float speed = 2;  // 怪物的速度
    
        internal int m_life;
        internal int m_maxlife;
    
        public System.Action<Enmy> onDeath;
        void Start () {
            ShowEffect();
        }
        
        void Update () {
            RorateTo();
            MoveTo();
        }
    
        public void MoveTo()
        {
            Vector3 pos1 = this.transform.position; // 当前怪物所在位置
            Vector3 pos2 = Vector3.zero;
            if (curNode!=null)
            {
                 pos2 = curNode.transform.position; // 起始点的位置
            }
            
            // 两者之间的距离差值
            float dis = Vector2.Distance(new Vector2(pos1.x, pos1.z), new Vector2(pos2.x, pos2.z));
            
            // 判断目的地
            if (dis < 0.3f)  // 到达目的地
            {
                if (curNode.next == null)
                {
                    DestroyMe();
                }
                else {
                    curNode = curNode.next; // 如果还有下个点,那么当前点就是起始点
                }
            }
            transform.Translate(new Vector3(0, 0, speed * Time.deltaTime)); // 移动
        }
    
        // internal : 只能在程序集中访问的意思
        internal void TakeDamage()
        {   // 加载粒子资源
            var p = Resources.Load("CFX2_SoulsEscape Rainbow");
            // 实例化(预制物,预制物位置,预制物角度)
            Instantiate(p, transform.position, Quaternion.identity);
    
            DestroyMe(); // 摧毁自己
        }
    
        // 例子特效
        void ShowEffect()
        {
            var p = Resources.Load("CFX2_EnemyDeathSkull");
            Instantiate(p, transform.position, Quaternion.identity);
        }
    
        // 旋转视角
        public void RorateTo()
        {   // 拿到当前对象的欧拉值的Y轴角度
            //http://wiki.ceeger.com/script:unityengine:classes:transform:transform.eulerangles
            float cur = this.transform.eulerAngles.y;
            // 朝向当前点的方向
            transform.LookAt(curNode.transform);
            // 移向目标(从当前的欧拉Y值,相对于父级的y轴变换旋转角度,目标速度*时间)
            float next = Mathf.MoveTowardsAngle(cur, this.transform.localEulerAngles.y, 120 * Time.deltaTime);
            //// 为当前对象赋值欧拉角
            this.transform.eulerAngles = new Vector3(0, next, 0);
        }
    
        private void DestroyMe()
        {
            onDeath(this);
            Destroy(gameObject);
        } 
    }
    

    怪物路径控制器脚本

    using UnityEngine;
    using System.Collections;
    
    public class PathManager : MonoBehaviour {
    
        public ArrayList PathNode;
    
        void Start () {
        
        }
        
        void Update () {
        
        }
    
        [ContextMenu("BuildPath")]
        void BuildPath()       // 编译路径
        {
            PathNode = new ArrayList(); // 初始化数组
            GameObject[] objs = GameObject.FindGameObjectsWithTag("pathnode"); // 找到所有Pathnode节点
            for (int i = 0; i < objs.Length; i++)
            {
                PathNode node = objs[i].GetComponent<PathNode>();  // 取出每一个节点
                PathNode.Add(node); 
            }
        }
    
        public void OnDrawGizmos()  // 窗口可见时,每一帧调用这个函数
        {
            if (PathNode == null) return;
            Gizmos.color = Color.blue;
            foreach (PathNode item in PathNode)
            {
                if (item.next != null) // 只要有下一个点
                {
                    Gizmos.DrawLine(item.transform.position, item.next.transform.position); //画线
                }
            }
        }
    }
    
    

    怪物路径

    using UnityEngine;
    using System.Collections;
    
    public class PathNode : MonoBehaviour
    {
    
        public PathNode parent;// PathNode类型的起始点
        public PathNode next;  // 下一个点
        void Start()
        {
    
        }
    
        void Update()
        {
    
        }
        public void SetNext(PathNode node)  // 设置下一个点
        {
            if (next != null)               // 如果下个点不存在
            {
                next.parent = null;        // 那么起始点也不存在
            }
            next = node;        //如果下个点存在,那么下个点就是传入的这个点
            node.parent = this;   // 起始点就是当前点
        }
    
        // 在窗口可见时,每一帧都会调用这个函数。在其中进行Gizmos的绘制,也就是辅助编辑的线框体
        void OnDrawGizmos()  // 画图 当绘制Gizmos
        {
            Gizmos.DrawIcon(this.transform.position, "Node.tif");
        }
    }
    

    关于怪物路径的编辑器的拓展工具条脚本(不用挂载,只需要放置在Editor文件下,没有就创建)

    using UnityEngine;
    using UnityEditor;
    using System.Collections;
    
    public class PathTool : ScriptableObject
    {
        static PathNode parent; // 静态起始点
    
        [MenuItem("PathTool/Creat PathNode")]
        static void GreatePathNoce()
        {
            // 创建一个新的路点
            GameObject go = new GameObject();
            go.AddComponent<PathNode>(); // 添加PathNode脚本
            go.name = "pathnode";
            // 设置标签
            go.tag = "pathnode";
            // 使该路点处于选择状态 (这个将绝不返回预设物或者不可修改的物体)
            Selection.activeTransform = go.transform;
        }
    
    
    
        [MenuItem("PathTool/Set Parent %q")]
        static void SetParent()  // 设置起始点
        {
            //  Selection.activeGameObject 返回激活的游戏物体。(在检查面板中显示)
            //  SelectionMode.Unfiltered 返回整个选择,
            //  Selection.GetTransforms(SelectionMode.Unfiltered).Length 
            //  允许对选择类型进行精细的控制,使用SelectionMode枚举类型。
            if (!Selection.activeGameObject || Selection.GetTransforms(SelectionMode.Unfiltered).Length > 1)
            {
                return;
            }
            // 如果选择的游戏对象的标签 = 点标签
            if (Selection.activeGameObject.tag.CompareTo("pathnode") == 0)
            {   // 那么起始点 = 选中游戏对象的所在脚本
                parent = Selection.activeGameObject.GetComponent<PathNode>();
                Debug.Log("设置" + parent.name + "起始点.");
            }
        }
    
        [MenuItem("PathTool/Set Next")]
        static void SetNextChild()
        {
            // 没有选择激活得游戏物体,并且没有起始点,并且所有选择的长度>1
            if (!Selection.activeGameObject || parent == null || Selection.GetTransforms(SelectionMode.Unfiltered).Length > 1)
            {
                return;
            }
            // 如果选择的激活的游戏对象的标签 == pathNode
            if (Selection.activeGameObject.tag.CompareTo("pathnode") == 0)
            {
                parent.SetNext(Selection.activeGameObject.GetComponent<PathNode>()); // 那么设置下一个点
                parent = null;
    
                Debug.Log("设置" + Selection.activeGameObject.name + "所选择激活的游戏对象的名字");
            }
            
        }
    }
    
    
    20.png

    相关文章

      网友评论

      本文标题:VR开发--虚拟与现实游戏(VR-狩猎)

      本文链接:https://www.haomeiwen.com/subject/ndtsyttx.html