美文网首页
VR 射击馆(三)抓取物体、拓展编辑器、Debug

VR 射击馆(三)抓取物体、拓展编辑器、Debug

作者: 烂醉花间dlitf | 来源:发表于2020-12-07 23:33 被阅读0次

    写在前面

    这个系列我删掉了一些东西,所以变成了碎碎念 + 收集癖的记录而已。en... 参考意义不大,建议随便看看...

    抓取物体

    判断当前在手的碰撞内的物体是否带有 GrabbedObject 组件,有的话判断是否只能单个手柄抓取,符合条件的话抓到手上。

    拓展编辑器

    Bow 类,有两个必须要有的动作,也就是拉弓和发射,两个动画名称一是可以写在一个专门存储常数的类中,比如类名叫做 ConstName,里面写上一句 public static readonly string DragAnimationName = "Copper_Bow_Armature|Draw"; 那么后面调用的时候可以直接写 ConstName.DragAnimationName。但是这样会导致出现很多静态变量,而且如果是分工开发的话,还需要去问清楚哪个名称是对应的哪个动画,因为 ConstName 类是我们自己写的。所以我希望能用中文直接把需求描述清楚,然后可以在 Inspector 面板直接赋值。

    效果

    运行结果

    代码

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor;
    
    [CanEditMultipleObjects, CustomEditor(typeof(Bow))]
    public class BowEditor : Editor
    {
        private SerializedProperty animationNameArray; // 存储动画名称的数组
        private bool showAnimationNameArray = false;
    
        private void OnEnable()
        {
    
            animationNameArray = serializedObject.FindProperty("animitionNameArray");
        }
        public override void OnInspectorGUI()
        {
            base.DrawDefaultInspector(); // 绘制原有的属性
            serializedObject.Update();
            showAnimationNameArray = EditorGUILayout.BeginFoldoutHeaderGroup(showAnimationNameArray, "动画名数组");
            if (showAnimationNameArray)
            {
                EditorGUILayout.HelpBox("动画数量至少为 2,前两个分别为拉弓和发射 ", MessageType.Info);
                EditorGUI.indentLevel++; // 缩进加一
                animationNameArray.arraySize = Mathf.Clamp(EditorGUILayout.IntField("动画数量", animationNameArray.arraySize), 2, int.MaxValue);
                EditorGUI.indentLevel++;
                EditorGUILayout.PropertyField(animationNameArray.GetArrayElementAtIndex(0), new GUIContent("拉弓", "在 Animation 中拉弓对应的动画名"));
                EditorGUILayout.PropertyField(animationNameArray.GetArrayElementAtIndex(1), new GUIContent("发射", "在 Animation 中发射对应的动画名"));
    
                for (int i = 2; i < animationNameArray.arraySize; i++)
                {
                    EditorGUILayout.PropertyField(animationNameArray.GetArrayElementAtIndex(i), new GUIContent("动画名_" + (i + 1), "在 Animation 中对应的动画名"));
                }
            }
            EditorGUILayout.EndFoldoutHeaderGroup();
    
            serializedObject.ApplyModifiedProperties();
        }
    
    }
    

    这里要注意一下 EditorGUILayout.BeginFoldoutHeaderGroup 这个函数只有 2019 以上才有,老坑了。

    // 在 Bow 中数组的定义    
    [SerializeField,HideInInspector]
    private string[] animitionNameArray; // 在编辑器中赋值,第一个是 Drag,第二个是 Fire
    

    觉得在代码中直接写 animitionNameArray[0]animitionNameArray[1] 不好看的,还可以写一个下面的枚举。

        private enum AnimationName : byte
        {
            Drag,
            Fire,
            MaxNameNum
        };
    

    Debug 相关

    直接在屏幕上画出轨迹

    下面是效果图。

    运行结果
    是使用的 OnPostRender 这个函数,这个需要挂在相机上。这个仅供测试用,因为数组里面的元素一直在增加。然后 WorldPositionToIdentity 这个函数在单独的一个相机的情况是可以按照注释那么写的,但是如果是 vr 设备,两个相机个渲染一半屏幕的话,就只能按照代码中的来写。
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class DrawTrail : MonoBehaviour
    {
        public Transform ob;
        private Material material;
        private Camera came;
        private List<Vector3> vertexs;
    
        private void Awake()
        {
            came = GetComponent<Camera>();
            vertexs = new List<Vector3>();
        }
    
        void OnPostRender()
        {
            Shader shader = Shader.Find("ZhangQr/Debug/GraphicDebug");
            material = new Material(shader);
            
            GL.PushMatrix();
            GL.LoadOrtho();
            GL.Begin(GL.LINE_STRIP);
            material.SetPass(1);
            vertexs.Add(WorldPositionToIdentity(ob.position));
            foreach(Vector3 v in vertexs)
            {
                GL.Vertex(v);
            }
            GL.End();
            GL.PopMatrix();
        }
    
        private Vector3 WorldPositionToIdentity(Vector3 ob)
        {
            //Vector3 screen_position = camera.WorldToScreenPoint(ob);
            //return new Vector3(screen_position.x / Screen.width, screen_position.y / Screen.height, 0); // 这种方法在 Pico 的双相机中不能使用
    
            Vector3 viewport_position = came.WorldToViewportPoint(ob);
            return new Vector3(viewport_position.x, viewport_position.y, 0);
        }
    
    }
    
    

    下面是 shader 的写法,因为后面需要画两种颜色的线,所以写了两个 pass。

    Shader "ZhangQr/Debug/GraphicDebug"
    {
        Properties
        {
            _ColorNormal ("ColorNormal", Color) = (0,0,1,1)
            _ColorHighlight("HighLight",Color) = (1,0,0,1)
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
                fixed4 _ColorNormal;
                fixed4 _ColorHighlight;
    
                float4 vert (float4 v:POSITION):SV_POSITION
                {
                    return UnityObjectToClipPos(v);
                }
    
                fixed4 frag (float4 i : SV_POSITION) : SV_Target
                {
                    return _ColorNormal;
                }
                ENDCG
            }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
                fixed4 _ColorNormal;
                fixed4 _ColorHighlight;
    
                float4 vert (float4 v:POSITION):SV_POSITION
                {
                    return UnityObjectToClipPos(v);
                }
    
                fixed4 frag (float4 i : SV_POSITION) : SV_Target
                {
                    return _ColorHighlight;
                }
                ENDCG
            }
        }
    }
    

    DebugText 单例

    因为是 VR 设备,所以很多手感之类的都需要在运行过程中去调整,所以一个 World 渲染模式的 Text 非常重要,这里有两个功能:1、全局调用,只需要执行一句DebugText.Instance().SetInfo("drag distance", distance.ToString());,并且如果没有在持续刷新的话,还会标记 (dirty) 2、捕获异常和错误并显示,效果如下:

    Debug 面板
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Text;
    using UnityEngine;
    using TMPro;
    
    
    public class DebugText : MonoBehaviour
    {
        private class OneInfomation
        {
            public bool isDirty;
            public string info;
        };
        private static DebugText _instance;
    
        private static TMP_Text debugText;
        private static TMP_Text errorText;
        public int DebugNo = 1;
        private Dictionary<string, OneInfomation> InfoDictionary = new Dictionary<string, OneInfomation>();
    
        private void Awake()
        {
            Application.logMessageReceived += ErrorHandler;
        }
        public static DebugText Instance()
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<DebugText>();
                debugText = _instance.gameObject.GetComponent<TMP_Text>();
                errorText = _instance.transform.parent.Find("ErrorText").GetComponent<TMP_Text>();
            }
            return _instance;
        }
    
        public void SetInfo(string titile, string info)
        {
            OneInfomation one_info = new OneInfomation
            {
                isDirty = false,
                info = info
            };
            InfoDictionary[titile] = one_info;
        }
    
        public void SetError(string error)
        {
            errorText.text += error + '\n';
        }
    
        private void Update()
        {
            StringBuilder sb = new StringBuilder("Debug No:" + DebugNo + '\n');
            foreach (KeyValuePair<string, OneInfomation> pair in InfoDictionary)
            {
                string d = pair.Value.isDirty ? "(Dirty)" : "";
                sb.Append(pair.Key + d + ": " + pair.Value.info + '\n');
                pair.Value.isDirty = true;
            }
            if(debugText!= null)
            {
                debugText.text = sb.ToString();
            }
        }
    
        void ErrorHandler(string logString, string stackTrace, LogType type)
        {
            if(type == LogType.Error||type == LogType.Exception)
            {
                SetInfo(logString, stackTrace);
            }
        }
    }
    

    生成球

    这是一个看起来很 Low,但我觉得很有用的方法,想出来是因为我想知道箭的每一段 Ray 是不是连续的,所以使用 Debug.DrawLine 来画出来,但画出来是一整条线,我又想知道每一小段的分界点在哪,所以使用这个下面的方式可以用小球来作为分界点,其实就是弥补了 Debug 没有 DraySphere 的遗憾吧(?)

    GameObject ob = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    ob.transform.position = transform.position;
    ob.transform.localScale = Vector3.one * 0.02f;
    

    选择标记

    这个不多说了,选一个标记可以方便定位想要观测的物体。


    标记

    单独执行方法

    这个可以直接在 Inspector 面板上右击,然后执行这个方法,但只能是无参的。

     [ContextMenu("TestFunction")]
    

    拓展菜单

    这个应该属于拓展编辑器了吧,因为在 Debug 的时候,需要非常频繁的使两个手柄分别放到弓上和弦上,所以直接搞个快捷键会大大提高效率。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor;
    
    public class EditorTest:MonoBehaviour
    {
        [MenuItem("MyTools/SetPosture _g")]
        public static void Postrue1()
        {
            MainControlHandMode.Instance().controller0.position =FindObjectOfType<Bow>().transform.position;
            MainControlHandMode.Instance().controller1.position = FindObjectOfType<Bow>().getStringWorldPosition();
        }
    }
    
    

    相关文章

      网友评论

          本文标题:VR 射击馆(三)抓取物体、拓展编辑器、Debug

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