当前位置:网站首页>Unity表格配置编辑工具
Unity表格配置编辑工具
2022-08-04 05:25:00 【丁小未】
更多优质文章:[Aladdin的博客](http://dingxiaowei.cn)
前言
游戏开发中表格配置是必有的功能,我们会将一些游戏数据配置在表格中,一般流程是Excel编辑数据,然后导出C#的Model类和Unity中可用的二进制或者json/xml数据等,但如果碰到要一个需求,策划想要频繁修改数据并且在游戏中测试,这就发现我们需要频繁的在Excel中修改然后导出,然后启动Unity测试,这样的流程就显得很繁琐,而且我们不能保证我们填写的数据就是正确的,难免有手滑手误的时候,但也有针对这种情况的解决方案是程序写一个表格检查工具来检查Excel配置的数据书否合法,但还是显得比较麻烦,针对这一问题可以做的优化,在Unity中写一个Editor编辑工具来编辑对应的Excel数据,并且对应的数据根据类型来给一个约束,例如:范围类型的数据,可以配置一个Range滑动类型,这样策划修改的数据就不会超出这个范围,再比如配置一个bool类型,我们给一个toggle选择,如果其他类型我们可以写一个数据监测,如果策划配置的数据不正确会立马给出提示,这样就能确保数据的正确性,并且可以及时的在游戏中进行测试,确保配置的数据是自己想要的效果,然后在Editor中保存到Excel,就不需要再Excel中进行配置。Excel的读写我用的NPOI,然后Excel类型的读取我用的反射,这样可以确保策划可以随意的修改Excel的字段类型确保工具的健壮性。关于C#的反射,不熟的或者不了解的可以看我之前写的一篇,关于C#的反射,你真的运用自如嘛?
效果
编辑数据区域是简单的编辑修改,没有对类型做约束和类型指定和判断。
代码
using Base.Framework.ExcelTool;
using Game.Data;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
public class SkillTableEditor : EditorWindow
{
Vector2 scrollPos;
Vector2 scrollEditorPos;
Vector2 scrollEditPos;
PropertyInfo[] properties;
List<skills_raw> skillDatas;
int currentSelectIndex = 0;
List<bool> toggleValues = new List<bool>();
List<int> widths = new List<int>();
skills_raw tempModifyData;
List<string> tempModifyDataValues;
[MenuItem("Tools/Excel/SkillTableEditor")]
public static void GetWindow()
{
Rect rect = new Rect(0, 0, 1920, 656);
var window = EditorWindow.GetWindowWithRect(typeof(SkillTableEditor), rect, true, "技能编辑");
window.Show();
}
private void OnEnable()
{
Base.Framework.SQLite4Unity3d.SqliteLoder.Load(Application.streamingAssetsPath);
skillDatas = Game.Data.skills_raw.GetDataList();
properties = typeof(skills_raw).GetProperties();
}
private void OnDisable()
{
BDFramework.Sql.SqliteHelper.DB.Close();
}
private GUIStyle GetStyle(int section, TextAnchor anchor = TextAnchor.MiddleCenter, int fontSize = 12)
{
return new GUIStyle()
{
alignment = anchor,
fontSize = fontSize,
normal = new GUIStyleState()
{
textColor = new Color(1, 1, 1),
},
};
}
private int GetCurrentProperityWidth(int section, int defaultWidth = 30)
{
return (properties[section].Name.Length / 5 + 1) * defaultWidth;
}
private void OnGUI()
{
GUI.Label(new Rect(0, 0, 600, 40), "注意:加载数据需要在游戏运行的时候,Excel->DB必须在游戏非运行的时候点!");
GUILayout.Space(30);
DrawTableDatas();
GUILayout.Space(30);
if (currentSelectIndex > 0)
{
GUILayout.Label(new GUIContent("编辑区域"), new GUIStyle()
{
alignment = TextAnchor.MiddleLeft,
fontSize = 16,
normal = new GUIStyleState()
{
textColor = new Color(1, 1, 1),
}
});
GUILayout.Space(30);
EditorGUILayout.BeginHorizontal();
scrollEditorPos = EditorGUILayout.BeginScrollView(scrollEditorPos, GUILayout.Width(1850), GUILayout.Height(100));
//显示title
GUILayout.BeginHorizontal();
for (int section = 0; section < NumberOfSectionsInTableView(); section++)
{
GUILayout.Label(new GUIContent(properties[section].Name), GetStyle(section), GUILayout.Width(GetCurrentProperityWidth(section, 33)));
}
GUILayout.EndHorizontal();
GUILayout.Space(20);
//显示数据
GUILayout.BeginHorizontal();
for (int i = 0; i < properties.Length; i++)
{
tempModifyDataValues[i] = EditorGUILayout.TextField(tempModifyDataValues[i], GetStyle(i), GUILayout.Width(GetCurrentProperityWidth(i, 33)));
}
GUILayout.EndHorizontal();
EditorGUILayout.EndScrollView();
EditorGUILayout.EndHorizontal();
GUILayout.Space(20);
GUILayout.BeginHorizontal();
if (GUILayout.Button("保存数据到Excel"))
{
if (tempModifyData != null)
{
var mBuilder = new ExcelConverter();
List<ExcelDataValue[]> values = new List<ExcelDataValue[]>();
List<string> attributesStringValues = new List<string>();
var assembly = typeof(skills_exceldata).Assembly;
for (int i = 0; i < properties.Length; i++)
{
var v = properties[i].GetValue(tempModifyData);
foreach (var m in GetExtensionMethods(assembly, v.GetType()))
{
if (m.Name.Contains("GetSaveToExcelStringValue"))
{
var stringValue = m.Invoke(null, new object[] { v });
attributesStringValues.Add(stringValue.ToString());
}
}
}
var attributesValues = attributesStringValues.ToArray();
ExcelDataValue[] datavalues = new ExcelDataValue[attributesValues.Length];
for (int i = 0; i < attributesValues.Length; i++)
{
datavalues[i] = new ExcelDataValue(attributesValues[i]);
}
values.Add(datavalues);
mBuilder.WriteDatas("skills", values);
Debug.Log("保存修改");
}
else
{
Debug.LogError("没有选择要编辑保存的数据");
}
}
if (GUILayout.Button("保存测试数据"))
{
Debug.Log("测试数据");
SaveModityDataToTempSkillData();
Game.Data.skills_raw.SetTestData(tempModifyData);
if (tempModifyData.id > skillDatas.Count)
{
skillDatas.Add(tempModifyData);
}
currentSelectIndex = 0;
}
GUILayout.EndHorizontal();
}
else
{
GUILayout.Label(new GUIContent("选择某一行编辑数据"), new GUIStyle() { fontSize = 16, normal = new GUIStyleState() { textColor = new Color(0.8f, 0, 0) } });
if (GUILayout.Button("添加数据"))
{
tempModifyData = new skills_raw()
{
id = skillDatas.Count + 1,
loc_skill_name = "LOC_DASH",
skill_type = 1,
state_mask = 3,
prepare_time = 0,
duration = 1,
end_time = 0,
accept_move_control = false,
follow_move_dir = true,
move_speed = Vector3.zero,
disable_gravity = false,
jump_height = 0,
projectile_id = 0,
projectile_dir = Vector3.zero,
//weapon_projectile_index = 1,
skill_param_1 = 0,
skill_param_2 = 0,
skill_param_3 = 0,
};
GetDataStringValues();
currentSelectIndex = tempModifyData.id;
toggleValues[0] = false;
}
}
}
IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
Type extendedType)
{
var query = from type in assembly.GetTypes()
where type.IsSealed && !type.IsGenericType && !type.IsNested
from method in type.GetMethods(BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
where method.GetParameters()[0].ParameterType == extendedType
select method;
return query;
}
private void GetDataStringValues()
{
if (tempModifyDataValues == null)
tempModifyDataValues = new List<string>();
else
tempModifyDataValues.Clear();
for (int i = 0; i < properties.Length; i++)
{
var value = properties[i].GetValue(tempModifyData);
if (value == null)
{
throw new Exception("对象字段值不存在");
}
tempModifyDataValues.Add(value.ToString());
}
}
/// <summary>
/// convert string to Type
/// </summary>
/// <param name="obj"></param>
/// <param name="type"></param>
/// <returns></returns>
private object ConvertObject(object obj, Type type)
{
if (type == null) return obj;
if (obj == null) return type.IsValueType ? Activator.CreateInstance(type) : null;
Type underlyingType = Nullable.GetUnderlyingType(type);
if (type.IsAssignableFrom(obj.GetType())) // 如果待转换对象的类型与目标类型兼容,则无需转换
{
return obj;
}
else if ((underlyingType ?? type).IsEnum) // 如果待转换的对象的基类型为枚举
{
if (underlyingType != null && string.IsNullOrEmpty(obj.ToString())) // 如果目标类型为可空枚举,并且待转换对象为null 则直接返回null值
{
return null;
}
else
{
return Enum.Parse(underlyingType ?? type, obj.ToString());
}
}
else if (typeof(IConvertible).IsAssignableFrom(underlyingType ?? type))
{
try
{
return Convert.ChangeType(obj, underlyingType ?? type, null);
}
catch
{
return underlyingType == null ? Activator.CreateInstance(type) : null;
}
}
else if (type.IsAssignableFrom(typeof(Vector3)))
{
string v = obj.ToString();
v = v.Replace("(", "").Replace(")", "");
var strValues = v.Split(',');
return new Vector3(Convert.ToSingle(strValues[0]), Convert.ToSingle(strValues[1]), Convert.ToSingle(strValues[2]));
}
else
{
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(obj.GetType()))
{
return converter.ConvertFrom(obj);
}
ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor != null)
{
object o = constructor.Invoke(null);
PropertyInfo[] propertys = type.GetProperties();
Type oldType = obj.GetType();
foreach (PropertyInfo property in propertys)
{
PropertyInfo p = oldType.GetProperty(property.Name);
if (property.CanWrite && p != null && p.CanRead)
{
property.SetValue(o, ConvertObject(p.GetValue(obj, null), property.PropertyType), null);
}
}
return o;
}
}
return obj;
}
private void SaveModityDataToTempSkillData()
{
for (int i = 0; i < properties.Length; i++)
{
var v = ConvertObject(tempModifyDataValues[i], properties[i].PropertyType);
properties[i].SetValue(tempModifyData, v);
}
}
void DrawTableDatas()
{
if (properties == null)
{
Debug.LogError("properties为空,需要在运行的情况下,点击SkillEditor功能");
return;
}
EditorGUILayout.BeginHorizontal();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(1850), GUILayout.Height(350));
EditorGUILayout.BeginHorizontal();
if (toggleValues.Count < 1)
toggleValues.Add(true);
if (toggleValues[0] = EditorGUILayout.Toggle(toggleValues[0]))
{
if (currentSelectIndex != 0)
{
ChangeSelect(0);
}
}
widths.Clear();
for (int i = 0; i < properties.Length; i++)
{
int w = (properties[i].Name.Length / 5 + 1) * 34;
widths.Add(w);
EditorGUILayout.LabelField(properties[i].Name, GUILayout.Width(w));
}
EditorGUILayout.EndHorizontal();
for (int i = 0; i < skillDatas.Count; i++)
{
EditorGUILayout.BeginHorizontal();
if (toggleValues.Count <= i + 1)
toggleValues.Add(false);
if (toggleValues[i + 1] = EditorGUILayout.Toggle(toggleValues[i + 1]))
{
if (currentSelectIndex != i + 1)
{
ChangeSelect(i + 1);
}
}
var data = skillDatas[i];
for (int j = 0; j < properties.Length; j++)
{
EditorGUILayout.LabelField(properties[j].GetValue(data).ToString(), GUILayout.Width(widths[j]));
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndHorizontal();
}
void ChangeSelect(int index)
{
if (toggleValues.Count < 1)
throw new Exception("没有toggle组");
if (index < 0 || index > toggleValues.Count - 1)
throw new Exception("toggle索引超过范围");
if (currentSelectIndex != index && currentSelectIndex <= skillDatas.Count)
{
toggleValues[currentSelectIndex] = false;
toggleValues[index] = true;
}
currentSelectIndex = index;
Debug.Log("选择" + index);
if (index > 0)
{
tempModifyData = skillDatas[index - 1];
GetDataStringValues();
}
}
int NumberOfSectionsInTableView()
{
return properties.Length;
}
}
下载
https://gitee.com/dingxiaowei/ExcelConfigEditor
更多精品教程
http://dingxiaowei.cn 拷贝到浏览器访问
边栏推荐
- Teenage Achievement Hackers Need These Skills
- C专家编程 第5章 对链接的思考 5.1 函数库、链接和载入
- npm安装依赖报错npm ERR! code ENOTFOUNDnpm ERR! syscall getaddrinfonpm ERR! errno ENOTFOUND
- flink cdc一启动,源端Oracle那台服务器的CPU就飙升到80%以上,会是啥原因呢?
- 嵌入式系统驱动初级【4】——字符设备驱动基础下_并发控制
- 力扣:96.不同的二叉搜索树
- The 2022 PMP exam has been delayed, should we be happy or worried?
- el-Select selector bottom fixed
- [One step in place] Jenkins installation, deployment, startup (complete tutorial)
- MySql数据恢复方法个人总结
猜你喜欢
TensorRT例程解读之语义分割demo
Get the selected content of the radio box
企业需要知道的5个 IAM 最佳实践
MySQL日志篇,MySQL日志之binlog日志,binlog日志详解
8、自定义映射resultMap
idea设置识别.sql文件类型以及其他文件类型
Shocked, 99.9% of the students didn't really understand the immutability of strings
C1认证之web基础知识及习题——我的学习笔记
8.03 Day34---BaseMapper查询语句用法
Can 't connect to MySQL server on' localhost3306 '(10061) simple solutions
随机推荐
Can 't connect to MySQL server on' localhost3306 '(10061) simple solutions
The 2022 PMP exam has been delayed, should we be happy or worried?
心余力绌:企业面临的软件供应链安全困境
Write golang simple C2 remote control based on gRPC
7、特殊SQL的执行
8、自定义映射resultMap
C语言 -- 操作符详解
The string class introduction
[Cloud Native--Kubernetes] Pod Resource Management and Probe Detection
7.16 Day22---MYSQL(Dao模式封装JDBC)
嵌入式系统驱动初级【4】——字符设备驱动基础下_并发控制
应届生软件测试薪资大概多少?
解决安装nbextensions后使用Jupyter Notebook时出现template_paths相关错误的问题
符号表
9. Dynamic SQL
TSF微服务治理实战系列(一)——治理蓝图
Cannot read properties of null (reading 'insertBefore')
C Expert Programming Chapter 4 The Shocking Fact: Arrays and pointers are not the same 4.4 Matching declarations to definitions
(Kettle) pdi-ce-8.2 连接MySQL8.x数据库时驱动问题之终极探讨及解决方法分析
npm安装依赖报错npm ERR! code ENOTFOUNDnpm ERR! syscall getaddrinfonpm ERR! errno ENOTFOUND