最近写一个小的编辑器功能,比较简单和入门,这里做一个简单教程。


因为最后可能需要重新编辑生成好的预设文件,要返回编辑,所以一些数据要保存下来,所以利用了userData保存数据。
下面的图是保存预设后的meta文件,可以看到userData数据。

本来这个在业务中,大概是通过3个图片生成一个地图预设,最后把一些关键输入保存到meta文件的userData数据中。文章中删除了业务代码,只有自定义面板部分,代码不是全面的代码。
这里只是讲解下面板是如何摆放组件并处理。
首先我们创建一个MazeEditor.cs脚本,主要用来记录我们填写的数据。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//UTF8 说明
public class MazeEditor : MonoBehaviour
{
[HideInInspector]
public GameObject OriginObject; //起点
[HideInInspector]
public GameObject StarObject; //星星
[HideInInspector]
public string mapName;
[HideInInspector]
public Object mapPrefab;
[HideInInspector]
[TooltipAttribute("地板图")]
public Sprite plane;
[HideInInspector]
[TooltipAttribute("高墙")]
public Sprite wall;
[HideInInspector]
[TooltipAttribute("底障碍")]
public Sprite slow_wall;
[HideInInspector]
public GameObject origin;
[HideInInspector]
public List<GameObject> stars;
[HideInInspector]
public bool mapCreated;
[HideInInspector]
public GameObject mapRoot;
[HideInInspector]
public GameObject starRoot;
//某些星星被删除掉了。视图里也要删除
public void StarDelete()
{
}
public void Save()
{
}
public void Clear()
{
mapCreated = false;
stars.Clear();
if (starRoot != null)
GameObject.DestroyImmediate(starRoot.gameObject);
if (mapRoot != null)
GameObject.DestroyImmediate(mapRoot.gameObject);
starRoot = null;
mapRoot = null;
plane = null;
wall = null;
slow_wall = null;
mapName = "";
}
}
然后是CreateMapEditor脚本,需要放入Editor文件夹。
using System.Collections.Generic;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
//UTF8 说明
[CustomEditor(typeof(MazeEditor))]
public class CreateMapEditor : Editor
{
int layerBuild;
SerializedProperty m_Star;
bool showsetting;
bool showMenu1;
bool showMenu2;
void OnEnable()
{
// 从GameObject脚本中获取对象以显示在检查器中
m_Star = serializedObject.FindProperty("stars");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI(); //显示
MazeEditor myScript = (MazeEditor)target;
if (!myScript.mapCreated)
{
showsetting = EditorGUILayout.Foldout(showsetting, "Flag");
if (showsetting)
{
EditorGUILayout.LabelField("标记位置的对象,请勿修改。");
myScript.OriginObject = (GameObject)EditorGUILayout.ObjectField("Origin (起点标记)", myScript.OriginObject, typeof(GameObject), true);
myScript.StarObject = (GameObject)EditorGUILayout.ObjectField("Star (星星标记)", myScript.StarObject, typeof(GameObject), true);
EditorGUILayout.Space(10);
}
}
showMenu1 = EditorGUILayout.Foldout(showMenu1, "MapSetting");
if (showMenu1)
{
myScript.mapName = EditorGUILayout.TextField("MapName (地图名)", myScript.mapName);
myScript.plane = (Sprite)EditorGUILayout.ObjectField("Plane (地板图)", myScript.plane, typeof(Sprite), false);
myScript.wall = (Sprite)EditorGUILayout.ObjectField("Wall (高墙)", myScript.wall, typeof(Sprite), false);
myScript.slow_wall = (Sprite)EditorGUILayout.ObjectField("Slow Wall (减速物)", myScript.slow_wall, typeof(Sprite), false);
if (myScript.mapRoot != null)
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Star, new GUIContent("Stars (星星)"));
if (EditorGUI.EndChangeCheck())
{
myScript.StarDelete();
}
}
//绘制按钮
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("创建地图"))
{
myScript.mapCreated = true;
Create();
}
if (myScript.mapCreated)
{
if (GUILayout.Button("创建星星"))
{
}
if (GUILayout.Button("保存"))
{
SaveMap();
Clear();
}
if (GUILayout.Button("清空"))
{
Clear();
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
}
showMenu2 = EditorGUILayout.Foldout(showMenu2, "Edit");
if (showMenu2)
{
myScript.mapPrefab = (Object)EditorGUILayout.ObjectField("MapPrefab (地图预设)", myScript.mapPrefab, typeof(Object), false);
if (GUILayout.Button("修改预设"))
{
Edit();
}
}
}
public void Create()
{
MazeEditor myScript = (MazeEditor)target;
if (myScript.plane == null)
{
Debug.Log("地面图片还没有。");
return;
}
//这里省略了业务代码。
}
public void Clear()
{
MazeEditor myScript = (MazeEditor)target;
myScript.Clear();
}
public void SaveMap()
{
MazeEditor myScript = (MazeEditor)target;
if (myScript.mapName.Length == 0)
{
Debug.LogWarning("请输入保存的名称。");
return;
}
string pathprefab = "Assets/ResAll/maps/" + myScript.mapName + ".prefab";
string bundlename = "reboot/maps/" + myScript.mapName;
myScript.mapRoot.name = myScript.mapName;
bool ok;
PrefabUtility.SaveAsPrefabAsset(myScript.mapRoot, pathprefab, out ok);
if (!ok)
{
Debug.LogError("保存失败:" + pathprefab);
return;
}
AssetImporter assetImporter = AssetImporter.GetAtPath(pathprefab); //得到Asset
assetImporter.assetBundleName = bundlename;
assetImporter.assetBundleVariant = "map";
MapData md = new MapData(); //一个保存Json的数据结构。
md.mapName = myScript.mapName;
md.spPlane = AssetDatabase.GetAssetPath(myScript.plane);
md.spWall = AssetDatabase.GetAssetPath(myScript.wall);
md.spSlow = AssetDatabase.GetAssetPath(myScript.slow_wall);
md.origin = myScript.origin.transform.position;
md.stars = new List<Vector3>();
for (int i = 0; i < myScript.stars.Count; i++)
{
md.stars.Add((Vector3)myScript.stars[i].transform.position);
}
var settings = new Newtonsoft.Json.JsonSerializerSettings();
settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
string str = Newtonsoft.Json.JsonConvert.SerializeObject(md, settings);
assetImporter.userData = str;
assetImporter.SaveAndReimport();
AssetDatabase.Refresh();
Debug.Log("保存成功:" + pathprefab);
myScript.Save();
}
void Edit()
{
MazeEditor myScript = (MazeEditor)target;
Object prefab = myScript.mapPrefab;
Clear(); //必须放在获取路径的后面
string pathprefab = AssetDatabase.GetAssetPath(prefab);
AssetImporter assetImporter = AssetImporter.GetAtPath(pathprefab); //得到Asset
string str = assetImporter.userData;
MapData md = Newtonsoft.Json.JsonConvert.DeserializeObject<MapData>(str);
myScript.mapName = md.mapName;
myScript.plane = (Sprite)AssetDatabase.LoadAssetAtPath(md.spPlane, typeof(Sprite));
myScript.wall = (Sprite)AssetDatabase.LoadAssetAtPath(md.spWall, typeof(Sprite));
myScript.slow_wall = (Sprite)AssetDatabase.LoadAssetAtPath(md.spSlow, typeof(Sprite));
Debug.Log("预设读取完毕:" + pathprefab);
}
}
首先要声明[CustomEditor(typeof(MazeEditor))],和继承自Editor,这样才能和Maze脚本联系起来。
[CustomEditor(typeof(MazeEditor))]
public class CreateMapEditor : Editor
核心就是要覆写OnInspectorGUI函数。
base.OnInspectorGUI();表示显示MazeEditor.cs的布局显示。
MazeEditor myScript = (MazeEditor)target;
通过这个可以获得MazeEditor的方法和属性。
showsetting = EditorGUILayout.Foldout(showsetting, “Flag”);
这个是一个箭头收放容器,点击箭头可以显示和隐藏。


EditorGUILayout.LabelField
是文本显示。
myScript.OriginObject = (GameObject)EditorGUILayout.ObjectField(“Origin (起点标记)”, myScript.OriginObject, typeof(GameObject), true);
这个就是可以放入一个对象,把对象给脚本的的变量,最后的是类型,true表示可以把Hierarchy里的对象拖入,否则只能拖入Assets文件夹里的资源。
后面的图片可以是:
myScript.plane = (Sprite)EditorGUILayout.ObjectField(“Plane (地板图)”, myScript.plane, typeof(Sprite), false);
因为类型是Sprite我们可以直接看到图片选择框。

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Star, new GUIContent("Stars (星星)"));
if (EditorGUI.EndChangeCheck())
{
myScript.StarDelete();
}
这两个是用于检测数据变化的,当调整了面板,可以去做一切其他事情。
EditorGUILayout.BeginHorizontal();
这个是布局,我们可以看到按钮可以横向摆放,最后结束用EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
是间距。
userData数据可以保存在meta文件中,它可以通过assetImporter.userData = str;的方式保存,因为是字符串,我这里用json转化成string的方式保存方便使用。
这就是一个简单的Inspector的自定义面板。
还有很多可以使用的组件,可以参考官方文档。
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2
我有这个html标记:我想得到这个:我如何使用Nokogiri做到这一点? 最佳答案 require'nokogiri'doc=Nokogiri::HTML('')您可以通过xpath删除所有属性:doc.xpath('//@*').remove或者,如果您需要做一些更复杂的事情,有时使用以下方法遍历所有元素会更容易:doc.traversedo|node|node.keys.eachdo|attribute|node.deleteattributeendend 关于ruby-Nokog
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些