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

2020-12-07  本文已影响0人  烂醉花间dlitf

写在前面

这个系列我删掉了一些东西,所以变成了碎碎念 + 收集癖的记录而已。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();
    }
}

上一篇 下一篇

猜你喜欢

热点阅读