写在前面
这个系列我删掉了一些东西,所以变成了碎碎念 + 收集癖的记录而已。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、捕获异常和错误并显示,效果如下:
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();
}
}
网友评论