private static void CreatePersonType(string gendir, string className, string parentName) { FileUtil.CreateFolder(gendir); var fullpath = gendir + "/" + className + ".cs"; if (File.Exists(fullpath)) return; var stream = File.CreateText(fullpath); stream.Write("using ReadyGamerOne.Data;\n" + "using ReadyGamerOne.Rougelike.Person;\n" + "using " + rootNs + ".Const;\n" + "namespace " + rootNs + ".Model.Person\n" + "{\n"); if(createControllers) stream.Write("\t[UsePersonController(typeof("+className+"Controller))]\n"); stream.Write("\tpublic partial class "+className+" :\n" + "\t\t"+parentName+"<"+className+">\n" + "\t{\n" + "\t\tpublic override string ResKey => PersonName."+className+";\n" + "\t\tpublic override void LoadData(CsvMgr data)\n" + "\t\t{\n" + "\t\t\t// 如果父类没有此方法,删掉就行\n"+ "\t\t\tbase.LoadData(data);\n"+ "\t\t\t// 在这里需要使用数据对类内变量进行初始化赋值(比如_hp,_maxhp,_attack,这三项自动生成在基类中了)\n" + "\t\t\t// 这个函数人物每次激活时都会调用\n" + "\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); }
/// <summary> /// 遍历Resources目录的时候操作文件的函数 /// </summary> /// <param name="fileInfo"></param> private static void OprateFile(FileInfo fileInfo) { // Debug.Log(fileInfo.FullName); if (fileInfo.FullName.EndsWith(".meta")) { // Debug.Log("跳过。meta"); return; } var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\") .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName)); var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName); var varName = FileUtil.FileNameToVarName(fileName); if (allResPathDic.ContainsKey(varName)) { Debug.LogWarning("相同Key: " + varName); Debug.LogWarning("已存:" + allResPathDic[varName]); Debug.LogWarning("现在:" + loadPath); } else { allResPathDic.Add(varName, loadPath); allResFileNameDic.Add(varName, fileName); otherResPathDic.Add(varName, loadPath); otherResFileNameDic.Add(varName, fileName); } }
private static void CreateControllerType(string gendir, string className, string parentName) { FileUtil.CreateFolder(gendir); var fullpath = gendir + "/" + className + ".cs"; if (File.Exists(fullpath)) { return; } var stream = File.CreateText(fullpath); stream.Write("namespace " + rootNs + ".Model.Person\n" + "{\n" + "\tpublic class " + className + " :\n" + "\t\t" + parentName + "\n" + "\t{\n" + "\t\tpublic override float MoveSpeed { get; set; }\n" + "\t\tpublic override void SetMoveable(bool state)\n" + "\t\t{\n" + "\t\t\tthrow new System.NotImplementedException();\n" + "\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); }
private static void CreateAbstractControllerType(string gendir, string curName, string controllerName, string parControllerName) { FileUtil.CreateClassFile( controllerName, rootNs + ".Model.Person", gendir, parControllerName, curName + "角色控制类,在角色类加UsePersonController属性的话会自动添加上去,使用InitController初始化", usingStatements: "using ReadyGamerOne.Rougelike.Person;\n", isAbstract: true); }
private static void StartGenerate(string rootNs, bool createControllers) { PersonTool.rootNs = rootNs; FileUtil.SearchDirectory( sourDir, OnOperateFile, true, OnOperateFolder); AssetDatabase.Refresh(); Debug.Log("生成完毕"); }
// foreach (var name in names) // { // if(name.GetAfterLastChar('/')=="self") // continue; // otherClassBody += "\t\tpublic const string "+name.Trim().GetAfterLastChar('/')+" = " + // "Path.Combine(Application.streamingAssetsPath, @\""+ name.Trim() +"\");\n"; // } /// <summary> /// 创建场景名文件 /// </summary> /// <param name="rootNs"></param> /// <param name="constNs"></param> /// <param name="autoDirName"></param> private static void CreateSceneNameClass(string rootNs, string constNs, string autoDirName) { var dic = new Dictionary <string, string>(); foreach (EditorBuildSettingsScene S in EditorBuildSettings.scenes) { var path = S.path; var name = Path.GetFileNameWithoutExtension(path); dic.Add(name, name); } FileUtil.CreateConstClassByDictionary( "SceneName", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDirName, rootNs + "." + constNs, dic); }
/// <summary> /// 遍历Resources目录时,操作目录的函数 /// </summary> /// <param name="dirInfo"></param> /// <param name="rootNs"></param> /// <param name="constNs"></param> /// <param name="autoDir"></param> private static bool OprateDir(DirectoryInfo dirInfo) { var dirName = dirInfo.FullName.GetAfterLastChar('\\'); if (dirName == "Resources") { return(true); } if (dirName.StartsWith("Global")) { return(false); } if (dirName.StartsWith("Class")) { autoClassName.Add(dirName.GetAfterSubstring("Class")); FileUtil.SearchDirectory(dirInfo.FullName, fileInfo => { if (!fileInfo.FullName.EndsWith(".meta")) { var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName); var varName = FileUtil.FileNameToVarName(fileName); var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\") .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName)); if (allResPathDic.ContainsKey(varName)) { Debug.LogWarning("出现同名资源文件:" + fileInfo); } else { allResPathDic.Add(varName, loadPath); allResFileNameDic.Add(varName, fileName); } } }, true); } return(true); }
private static bool OnOperateFolder(DirectoryInfo dirInfo) { // if (dirInfo.FullName!=sourDir && dirInfo.Name.StartsWith("Type") == false) // return false; var curName = dirInfo.Name; var parName = dirInfo.Parent.Name; var parControllerName = ""; var controllerName = ""; if (curName == FileUtil.GetDirName(sourDir)) { parName = "PoolDataPerson"; curName = "Person"; controllerName = rootNs + curName + "Controller"; parControllerName = "AbstractPersonController"; } else if (parName == FileUtil.GetDirName(sourDir)) { parName = rootNs + "Person"; curName = curName.GetAfterSubstring("Type"); controllerName = rootNs + curName + "Controller"; parControllerName = parName + "Controller"; } else { parName = rootNs + parName.GetAfterSubstring("Type"); curName = curName.GetAfterSubstring("Type"); controllerName = rootNs + curName + "Controller"; parControllerName = parName + "Controller"; } var genDir = Application.dataPath + "/" + rootNs + "/Model/Person" + dirInfo.GetSpecialPath("Type") + "/" + rootNs + curName; CreateAbstractPersonType(genDir, dirInfo, curName, parName); CreateAbstractControllerType(genDir, curName, controllerName, parControllerName); return(true); }
static void OnToolsGUI(string rootNs, string viewNs, string constNs, string dataNs, string autoDir, string scriptDir) { EditorGUILayout.Space(); _resourceManagerType = (ResourceManagerType)EditorGUILayout.EnumPopup("资源加载类型", _resourceManagerType); switch (_resourceManagerType) { #region ResourceManagerType.Resource case ResourceManagerType.Resource: EditorGUILayout.LabelField("自动常量文件生成目录", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir); EditorGUILayout.LabelField("常量工具类生成目录", Application.dataPath + "/" + rootNs + "/Utility/" + autoDir + "/ConstUtil.cs"); EditorGUILayout.Space(); createPathFile = EditorGUILayout.Toggle("是否生成资源路径文件", createPathFile); EditorGUILayout.Space(); if (GUILayout.Button("生成常量")) { GenerateResourcesConst(rootNs, constNs, autoDir); } break; #endregion #region ResourceManagerType.AssetBundle case ResourceManagerType.AssetBundle: var newestVersion = PlayerPrefs.GetString(VersionDefine.PrefKey_LocalVersion, "0.0"); if (GUILayout.Button("显示本地版本")) { var versionData = PlayerPrefs.GetString(newestVersion); Debug.Log("本地版本号:" + newestVersion + "\n版本信息:" + versionData); } EditorGUI.BeginChangeCheck(); newestVersion = EditorGUILayout.DelayedTextField("本地版本", newestVersion); if (EditorGUI.EndChangeCheck()) { PlayerPrefs.SetString(VersionDefine.PrefKey_LocalVersion, newestVersion); } EditorGUILayout.Space(); EditorGUILayout.LabelField("打包目录", assetToBundleDir); if (GUILayout.Button("设置要打包的目录")) { assetToBundleDir = EditorUtility.OpenFolderPanel("选择需要打包的目录", Application.dataPath, ""); } EditorGUILayout.Space(); EditorGUILayout.LabelField("输出目录", outputDir); if (GUILayout.Button("设置输出目录")) { outputDir = EditorUtility.OpenFolderPanel("选择输出目录", Application.dataPath, ""); } EditorGUILayout.Space(); assetBundleOptions = (BuildAssetBundleOptions)EditorGUILayout.EnumFlagsField("打包选项", assetBundleOptions); buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("目标平台", buildTarget); clearOutputDir = EditorGUILayout.Toggle("清空生成目录", clearOutputDir); EditorGUILayout.Space(); useForRuntime = EditorGUILayout.Foldout(useForRuntime, "使用用于生成运行时直接使用的AB包"); if (useForRuntime) { EditorGUILayout.LabelField("生成HotUpdatePath.cs路径", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir + "/HotUpdatePath.cs"); useWeb = EditorGUILayout.Toggle("是否使用网络", useWeb); EditorGUILayout.LabelField("游戏自身AB包名字", streamingAbName); EditorGUILayout.Space(); if (GUILayout.Button("重新生成常量类【会覆盖】")) { if (!outputDir.Contains(Application.streamingAssetsPath)) { Debug.LogError("运行时使用的AB包必须在StreamingAssets目录下"); return; } if (assetBundleNames.Count == 0) { Debug.LogError("AB包数组未空"); return; } CreatePathDataClass(rootNs, constNs, autoDir, assetBundleNames); AssetDatabase.Refresh(); Debug.Log("生成完成"); } } if (GUILayout.Button("开始打包", GUILayout.Height(3 * EditorGUIUtility.singleLineHeight))) { if (createPathDataClass) { if (!outputDir.Contains(Application.streamingAssetsPath)) { Debug.LogError("运行时使用的AB包必须在StreamingAssets目录下"); return; } } if (assetBundleNames.Count != 0) { assetBundleNames.Clear(); } if (!Directory.Exists(assetToBundleDir)) { Debug.LogError("打包目录设置异常"); return; } if (!Directory.Exists(outputDir)) { Debug.LogError("输出目录设置异常"); return; } if (clearOutputDir) { if (Directory.Exists(outputDir)) { //获取指定路径下所有文件夹 string[] folderPaths = Directory.GetDirectories(outputDir); foreach (string folderPath in folderPaths) { Directory.Delete(folderPath, true); } //获取指定路径下所有文件 string[] filePaths = Directory.GetFiles(outputDir); foreach (string filePath in filePaths) { File.Delete(filePath); } } } var builds = new List <AssetBundleBuild>(); foreach (var dirPath in System.IO.Directory.GetDirectories(assetToBundleDir)) { var dirName = new DirectoryInfo(dirPath).Name; var paths = new List <string>(); var assetNames = new List <string>(); FileUtil.SearchDirectory(dirPath, fileInfo => { if (fileInfo.Name.EndsWith(".meta")) { return; } var pureName = Path.GetFileNameWithoutExtension(fileInfo.FullName); assetNames.Add(pureName); var fileLoadPath = fileInfo.FullName.Replace("\\", "/") .Replace(Application.dataPath, "Assets"); var ai = AssetImporter.GetAtPath(fileLoadPath); ai.assetBundleName = dirName; paths.Add(fileLoadPath); }, true); assetBundleNames.Add(dirName); FileUtil.CreateConstClassByDictionary( dirName + "Name", Application.dataPath + "/" + rootNs + "/" + constNs, rootNs + "." + constNs, assetNames.ToDictionary(name => name)); builds.Add(new AssetBundleBuild { assetNames = paths.ToArray(), assetBundleName = dirName }); } if (createPathDataClass) { CreatePathDataClass(rootNs, constNs, autoDir, assetBundleNames); } BuildPipeline.BuildAssetBundles(outputDir, builds.ToArray(), assetBundleOptions, buildTarget); AssetDatabase.Refresh(); Debug.Log("打包完成"); } break; #endregion } }
private static void CreateAbstractPersonType(string gendir, DirectoryInfo dirInfo, string curName, string parName) { var className = rootNs + curName; var isRootPerson = new DirectoryInfo(dirInfo.FullName).FullName == new DirectoryInfo(sourDir).FullName; FileUtil.CreateFolder(gendir); var fileFullPath = gendir + "/" + className + ".cs"; if (File.Exists(fileFullPath)) { return; } var stream = File.CreateText(fileFullPath); stream.Write("using ReadyGamerOne.Rougelike.Person;\n"); if (dirInfo.HasValuableFiles()) { stream.Write("using System;\n" + "using " + rootNs + ".Data;\n"); } stream.Write("using UnityEngine;\n\n" + "namespace " + rootNs + ".Model.Person\n" + "{\n" + "\tpublic interface I" + className); if (!isRootPerson) { stream.Write(" : \n" + "\t\tI" + parName); } stream.Write("\n" + "\t{\n" + "\t}\n\n" + "\tpublic abstract class " + className + "<T>:\n" + "\t\t" + parName + "<T>,\n" + "\t\tI" + className + "\n" + "\t\twhere T : " + className + "<T>,new()\n" + "\t{\n"); if (isRootPerson) { stream.Write("\t\t#region Fields\n\n" + "\t\tprotected int _hp;\n" + "\t\tprotected int _maxHp;\n" + "\t\tprotected int _attack;\n\n" + "\t\t#endregion\n\n" + "\t\t#region ITakeDamageablePerson<T>\n\n" + "\t\tpublic override int Hp => _hp;\n" + "\t\tpublic override int MaxHp => _maxHp;\n" + "\t\tpublic virtual void OnTakeDamage(AbstractPerson takeDamageFrom, int damage)\n" + "\t\t{\n" + "\t\t\tDebug.Log($\"{CharacterName}收到来自{takeDamageFrom.CharacterName}的{damage}伤害\");\n\n" + "\t\t\t_hp -= damage;\n" + "\t\t\tif(_hp<0)\n" + "\t\t\t\tKill();\n" + "\t\t}\n\n" + "\t\t public void OnCauseDamage(AbstractPerson causeDamageTo, int damage)\n" + "\t\t{\n" + "\t\t\tDebug.Log($\"{CharacterName}对{causeDamageTo.CharacterName}造成{damage}伤害\");\n" + "\t\t}\n\n" + "\t\t#endregion\n"); } if (dirInfo.HasValuableFiles()) { stream.Write("\t\tpublic override Type DataType => typeof(" + curName + "Data);\n"); } stream.Write("\t}\n" + "}\n"); stream.Dispose(); stream.Close(); }
static void OnToolsGUI(string rootNs, string viewNs, string constNs, string dataNs, string autoDir, string scriptDir) { EditorGUILayout.Space(); _resourceManagerType = (ResourceManagerType)EditorGUILayout.EnumPopup("资源加载类型", _resourceManagerType); switch (_resourceManagerType) { #region ResourceManagerType.Resource case ResourceManagerType.Resource: EditorGUILayout.LabelField("自动常量文件生成目录", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir); EditorGUILayout.LabelField("常量工具类生成目录", Application.dataPath + "/" + rootNs + "/Utility/" + autoDir + "/ConstUtil.cs"); EditorGUILayout.Space(); createPathFile = EditorGUILayout.Toggle("是否生成资源路径文件", createPathFile); EditorGUILayout.Space(); if (GUILayout.Button("生成常量")) { #region 遍历Resources生成常量文件 var resourceDir = Application.dataPath + "/Resources"; var rootDir = Application.dataPath + "/" + rootNs; autoClassName.Clear(); otherResPathDic.Clear(); otherResFileNameDic.Clear(); allResPathDic.Clear(); allResFileNameDic.Clear(); foreach (var fullName in Directory.GetFileSystemEntries(resourceDir)) { // Debug.Log(fullName); if (Directory.Exists(fullName)) { //如果是文件夹 OprateDir(new DirectoryInfo(fullName), rootNs, constNs, autoDir); } else { //是文件 OprateFile(new FileInfo(fullName)); } } //生成其他常量文件 if (otherResPathDic.Count > 0) { // Debug.Log("创建文件OtherResPath"); FileUtil.CreateConstClassByDictionary("OtherResPath", rootDir + "/" + constNs + "/" + autoDir, rootNs + "." + constNs, otherResPathDic); FileUtil.CreateConstClassByDictionary("OtherResName", rootDir + "/" + constNs + "/" + autoDir, rootNs + "." + constNs, otherResFileNameDic); autoClassName.Add("OtherRes"); } //生成常量工具类 if (allResPathDic.Count > 0) { var content = "\t\tprivate System.Collections.Generic.Dictionary<string,string> nameToPath \n" + "\t\t\t= new System.Collections.Generic.Dictionary<string,string>{\n"; foreach (var kv in allResFileNameDic) { content += "\t\t\t\t\t{ @\"" + kv.Value + "\" , @\"" + allResPathDic[kv.Key] + "\" },\n"; } content += "\t\t\t\t};\n"; content += "\t\tpublic override System.Collections.Generic.Dictionary<string,string> NameToPath => nameToPath;\n"; FileUtil.CreateClassFile("AssetConstUtil", rootNs + ".Utility", rootDir + "/Utility/" + autoDir, parentClass: "ReadyGamerOne.MemorySystem.AssetConstUtil<AssetConstUtil>", helpTips: "这个类提供了Resources下文件名和路径字典访问方式,同名资源可能引起bug", fileContent: content, autoOverwrite: true); } AssetDatabase.Refresh(); Debug.Log("生成结束"); #endregion } break; #endregion #region ResourceManagerType.AssetBundle case ResourceManagerType.AssetBundle: var newestVersion = PlayerPrefs.GetString(VersionDefine.PrefKey_LocalVersion, "0.0"); if (GUILayout.Button("显示本地版本")) { var versionData = PlayerPrefs.GetString(newestVersion); Debug.Log("本地版本号:" + newestVersion + "\n版本信息:" + versionData); } EditorGUI.BeginChangeCheck(); newestVersion = EditorGUILayout.DelayedTextField("本地版本", newestVersion); if (EditorGUI.EndChangeCheck()) { PlayerPrefs.SetString(VersionDefine.PrefKey_LocalVersion, newestVersion); } EditorGUILayout.Space(); EditorGUILayout.LabelField("打包目录", assetToBundleDir); if (GUILayout.Button("设置要打包的目录")) { assetToBundleDir = EditorUtility.OpenFolderPanel("选择需要打包的目录", Application.dataPath, ""); } EditorGUILayout.Space(); EditorGUILayout.LabelField("输出目录", outputDir); if (GUILayout.Button("设置输出目录")) { outputDir = EditorUtility.OpenFolderPanel("选择输出目录", Application.dataPath, ""); } EditorGUILayout.Space(); assetBundleOptions = (BuildAssetBundleOptions)EditorGUILayout.EnumFlagsField("打包选项", assetBundleOptions); buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("目标平台", buildTarget); clearOutputDir = EditorGUILayout.Toggle("清空生成目录", clearOutputDir); EditorGUILayout.Space(); useForRuntime = EditorGUILayout.Foldout(useForRuntime, "使用用于生成运行时直接使用的AB包"); if (useForRuntime) { EditorGUILayout.LabelField("生成HotUpdatePath.cs路径", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir + "/HotUpdatePath.cs"); useWeb = EditorGUILayout.Toggle("是否使用网络", useWeb); EditorGUILayout.LabelField("游戏自身AB包名字", streamingAbName); EditorGUILayout.Space(); if (GUILayout.Button("重新生成常量类【会覆盖】")) { if (!outputDir.Contains(Application.streamingAssetsPath)) { Debug.LogError("运行时使用的AB包必须在StreamingAssets目录下"); return; } if (assetBundleNames.Count == 0) { Debug.LogError("AB包数组未空"); return; } CreatePathDataClass(rootNs, constNs, autoDir, assetBundleNames); AssetDatabase.Refresh(); Debug.Log("生成完成"); } } if (GUILayout.Button("开始打包", GUILayout.Height(3 * EditorGUIUtility.singleLineHeight))) { if (createPathDataClass) { if (!outputDir.Contains(Application.streamingAssetsPath)) { Debug.LogError("运行时使用的AB包必须在StreamingAssets目录下"); return; } } if (assetBundleNames.Count != 0) { assetBundleNames.Clear(); } if (!Directory.Exists(assetToBundleDir)) { Debug.LogError("打包目录设置异常"); return; } if (!Directory.Exists(outputDir)) { Debug.LogError("输出目录设置异常"); return; } if (clearOutputDir) { if (Directory.Exists(outputDir)) { //获取指定路径下所有文件夹 string[] folderPaths = Directory.GetDirectories(outputDir); foreach (string folderPath in folderPaths) { Directory.Delete(folderPath, true); } //获取指定路径下所有文件 string[] filePaths = Directory.GetFiles(outputDir); foreach (string filePath in filePaths) { File.Delete(filePath); } } } var builds = new List <AssetBundleBuild>(); foreach (var dirPath in System.IO.Directory.GetDirectories(assetToBundleDir)) { var dirName = new DirectoryInfo(dirPath).Name; var paths = new List <string>(); var assetNames = new List <string>(); FileUtil.SearchDirectory(dirPath, fileInfo => { if (fileInfo.Name.EndsWith(".meta")) { return; } var pureName = Path.GetFileNameWithoutExtension(fileInfo.FullName); assetNames.Add(pureName); var fileLoadPath = fileInfo.FullName.Replace("\\", "/") .Replace(Application.dataPath, "Assets"); var ai = AssetImporter.GetAtPath(fileLoadPath); ai.assetBundleName = dirName; paths.Add(fileLoadPath); }, true); assetBundleNames.Add(dirName); FileUtil.CreateConstClassByDictionary( dirName + "Name", Application.dataPath + "/" + rootNs + "/" + constNs, rootNs + "." + constNs, assetNames.ToDictionary(name => name)); builds.Add(new AssetBundleBuild { assetNames = paths.ToArray(), assetBundleName = dirName }); } if (createPathDataClass) { CreatePathDataClass(rootNs, constNs, autoDir, assetBundleNames); } BuildPipeline.BuildAssetBundles(outputDir, builds.ToArray(), assetBundleOptions, buildTarget); AssetDatabase.Refresh(); Debug.Log("打包完成"); } break; #endregion } }
public static void GenerateResourcesConst(string rootNs, string constNs, string autoDir) { var resourceDir = Application.dataPath + "/Resources"; var rootDir = Application.dataPath + "/" + rootNs; autoClassName.Clear(); otherResPathDic.Clear(); otherResFileNameDic.Clear(); allResPathDic.Clear(); allResFileNameDic.Clear(); foreach (var fullName in Directory.GetFileSystemEntries(resourceDir)) { // Debug.Log(fullName); if (Directory.Exists(fullName)) { //如果是文件夹 OprateDir(new DirectoryInfo(fullName), rootNs, constNs, autoDir); } else { //是文件 OprateFile(new FileInfo(fullName)); } } //生成其他常量文件 if (otherResPathDic.Count > 0) { // Debug.Log("创建文件OtherResPath"); FileUtil.CreateConstClassByDictionary("OtherResPath", rootDir + "/" + constNs + "/" + autoDir, rootNs + "." + constNs, otherResPathDic); FileUtil.CreateConstClassByDictionary("OtherResName", rootDir + "/" + constNs + "/" + autoDir, rootNs + "." + constNs, otherResFileNameDic); autoClassName.Add("OtherRes"); } //生成常量工具类 if (allResPathDic.Count > 0) { var content = "\t\tprivate System.Collections.Generic.Dictionary<string,string> nameToPath \n" + "\t\t\t= new System.Collections.Generic.Dictionary<string,string>{\n"; foreach (var kv in allResFileNameDic) { content += "\t\t\t\t\t{ @\"" + kv.Value + "\" , @\"" + allResPathDic[kv.Key] + "\" },\n"; } content += "\t\t\t\t};\n"; content += "\t\tpublic override System.Collections.Generic.Dictionary<string,string> NameToPath => nameToPath;\n"; FileUtil.CreateClassFile("AssetConstUtil", rootNs + ".Utility", rootDir + "/Utility/" + autoDir, parentClass: "ReadyGamerOne.MemorySystem.AssetConstUtil<AssetConstUtil>", helpTips: "这个类提供了Resources下文件名和路径字典访问方式,同名资源可能引起bug", fileContent: content, autoOverwrite: true); } AssetDatabase.Refresh(); Debug.Log("生成结束"); }
static void OnToolsGUI(string rootNs, string viewNs, string constNs, string dataNs, string autoDir, string scriptNs) { EditorGUILayout.Space(); EditorGUILayout.Space(); createPanelClasses = EditorGUILayout.Toggle("是否自动生成Panel类型", createPanelClasses); if (createPanelClasses) { EditorGUILayout.LabelField("自动生成Panel文件路径", Application.dataPath + "/" + rootNs + "/" + viewNs); } EditorGUILayout.Space(); createGameMgr = EditorGUILayout.Toggle("是否生成GameMgr", createGameMgr); if (createGameMgr) { _resourceManagerType = (ResourceManagerType)EditorGUILayout.EnumPopup("资源管理类型", _resourceManagerType); EditorGUILayout.LabelField("自动生成" + rootNs + "Mgr文件路径", Application.dataPath + "/" + rootNs + "/" + scriptNs); } EditorGUILayout.Space(); createMessage = EditorGUILayout.Toggle("是否生成空Message常量类", createMessage); if (createMessage) { EditorGUILayout.LabelField("生成Message.cs路径", Application.dataPath + "/" + rootNs + "/" + constNs + "/Message.cs"); } EditorGUILayout.Space(); createGlobalVar = EditorGUILayout.Toggle("是否重写GlobalVar类", createGlobalVar); if (createGlobalVar) { EditorGUILayout.LabelField("生成GlobalVar.cs路径", Application.dataPath + "/" + rootNs + "/Global/GlobalVar.cs"); } EditorGUILayout.Space(); createSceneNameClass = EditorGUILayout.Toggle("是否生成SceneName类", createSceneNameClass); if (createSceneNameClass) { EditorGUILayout.LabelField("生成SceneName.cs路径", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir + "/SceneName.cs"); } EditorGUILayout.Space(); if (GUILayout.Button("开启自动生成", GUILayout.Height(2 * EditorGUIUtility.singleLineHeight))) { #region 文件夹的创建 var resourceDir = Application.dataPath + "/Resources"; var rootDir = Application.dataPath + "/" + rootNs; if (!Directory.Exists(resourceDir)) { Directory.CreateDirectory(resourceDir); return; } FileUtil.CreateFolder(rootDir); FileUtil.CreateFolder(rootDir + "/" + constNs); FileUtil.CreateFolder(rootDir + "/" + constNs + "/" + autoDir); #endregion autoClassName.Clear(); otherResPathDic.Clear(); otherResFileNameDic.Clear(); allResPathDic.Clear(); allResFileNameDic.Clear(); foreach (var fullName in Directory.GetFileSystemEntries(resourceDir)) { if (Directory.Exists(fullName)) { //如果是文件夹 OprateDir(new DirectoryInfo(fullName)); } else { //是文件 OprateFile(new FileInfo(fullName)); } } #region 特殊类的生成 if (createPanelClasses && autoClassName.Contains("Panel")) { CreatePanelFile(Application.dataPath + "/Resources/ClassPanel", viewNs, constNs, rootNs, autoDir); } #endregion #region 定向生成特殊小文件 if (createGameMgr) { CreateMgr(rootNs, constNs, scriptNs, autoDir); } if (createGlobalVar) { FileUtil.CreateClassFile("GlobalVar", rootNs + ".Global", rootDir + "/Global", "ReadyGamerOne.Global.GlobalVar", "这里写当前项目需要的全局变量,调用GlobalVar时,最好调用当前这个类"); } if (createMessage) { FileUtil.CreateClassFile("Message", rootNs + "." + constNs, rootDir + "/" + constNs, helpTips: "自定义的消息写到这里"); } if (createSceneNameClass) { CreateSceneNameClass(rootNs, constNs, autoDir); } #endregion AssetDatabase.Refresh(); Debug.Log("生成结束"); } }
/// <summary> /// 创建用于使用AB包管理资源的路径定义类 /// </summary> /// <param name="rootNs"></param> /// <param name="constNs"></param> /// <param name="autoDir"></param> private static void CreatePathDataClass(string rootNs, string constNs, string autoDir, List <string> names) { var outputDir = ResourceMgr.outputDir.Replace(Application.streamingAssetsPath, ""); var mainAssetBundleName = new DirectoryInfo(ResourceMgr.outputDir).Name; var content = ""; var otherClassBody = "\n"; #region OriginBundleKey otherClassBody += "\tpublic class OriginBundleKey : ReadyGamerOne.MemorySystem.OriginBundleKey\n" + "\t{\n"; foreach (var name in names) { var pureName = name.GetAfterLastChar('/'); if (pureName == "Audio" || pureName == "File") { continue; } otherClassBody += "\t\tpublic const string " + name.Trim().GetAfterLastChar('/') + " = " + "@\"" + name.Trim() + "\";\n"; } otherClassBody += "\t}\n"; #endregion #region OriginBundleUtil otherClassBody += "\n\tpublic class OriginBundleUtil : OriginBundleUtil<OriginBundleUtil>\n" + "\t{\n"; #region KeyToName otherClassBody += "\t\tprivate Dictionary<string,string> _keyToName = new Dictionary<string,string>\n" + "\t\t{\n" + "\t\t\t{\"Self\" , @\"self\"},\n"; foreach (var name in names) { if (name.GetAfterLastChar('/') == "self") { continue; } otherClassBody += "\t\t\t{\"" + name.Trim().GetAfterLastChar('/') + "\" , " + "@\"" + name.Trim().ToLower() + "\"},\n"; } otherClassBody += "\t\t};\n"; otherClassBody += "\t\tpublic override Dictionary<string, string> KeyToName => _keyToName;\n"; #endregion #region KeyToPath otherClassBody += "\t\tprivate Dictionary<string,string> _keyToPath = new Dictionary<string,string>\n" + "\t\t{\n" + "\t\t\t{\"Self\" , Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", @\"self\")},\n"; foreach (var name in names) { if (name.GetAfterLastChar('/') == "self") { continue; } otherClassBody += "\t\t\t{\"" + name.Trim().GetAfterLastChar('/') + "\" , " + "Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", @\"" + name.Trim().ToLower() + "\")},\n"; } otherClassBody += "\t\t};\n"; otherClassBody += "\t\tpublic override Dictionary<string, string> KeyToPath => _keyToPath;\n"; #endregion #region NameToPath otherClassBody += "\t\tprivate Dictionary<string,string> _nameToPath = new Dictionary<string,string>\n" + "\t\t{\n" + "\t\t\t{@\"self\" , Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", @\"self\")},\n"; foreach (var name in names) { if (name.GetAfterLastChar('/') == "self") { continue; } otherClassBody += "\t\t\t{@\"" + name.Trim().ToLower() + "\" , " + "Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", @\"" + name.Trim().ToLower() + "\")},\n"; } otherClassBody += "\t\t};\n"; otherClassBody += "\t\tpublic override Dictionary<string, string> NameToPath => _nameToPath;\n"; #endregion otherClassBody += "\t}\n"; #endregion if (useWeb) { content = "\t\tpublic string OriginMainManifest => @Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", \"" + mainAssetBundleName + "\");\n" + "\t\tpublic string LocalMainPath => @Path.Combine(Application.persistentDataPath, \"AssetBundles\");\n" + "\t\tpublic string WebServeMainPath => @\"file:/C:\\Users\\ReadyGamerOne\\Downloads\\webserver\";\n" + "\t\tpublic string WebServeMainManifest => WebServeMainPath + \"\\\\ManifestFile\";\n" + "\t\tpublic string WebServeVersionPath => WebServeMainPath + \"\\\\ServeVersion.html\";\n" + "\t\tpublic string WebServeBundlePath => WebServeMainPath + \"\\\\AssetBundles\";\n" + "\t\tpublic string WebServeConfigPath => WebServeMainPath + \"\\\\ServeConfig\";\n" + "\t\tpublic Func<string, string> GetServeConfigPath => version =>$\"{WebServeConfigPath}/{version}.html\";\n" + "\t\tpublic Func<string, string, string> GetServeBundlePath => (bundleName,bundleVersion)=>$\"{WebServeBundlePath}/{bundleVersion}/{bundleName}\";\n" + "\t\tpublic Func<string, string, string> GetLocalBundlePath => (bundleName,bundleVersion)=>$\"{LocalMainPath}/{bundleVersion}/{bundleName}\";\n"; } else { content = "\t\tpublic string OriginMainManifest => @Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", \"" + mainAssetBundleName + "\");\n" + "\t\tpublic string LocalMainPath => null;\n" + "\t\tpublic string WebServeMainPath => null;\n" + "\t\tpublic string WebServeMainManifest => null;\n" + "\t\tpublic string WebServeVersionPath => null;\n" + "\t\tpublic string WebServeBundlePath => null;\n" + "\t\tpublic string WebServeConfigPath => null;\n" + "\t\tpublic Func<string, string> GetServeConfigPath => null;\n" + "\t\tpublic Func<string, string, string> GetServeBundlePath => null;\n" + "\t\tpublic Func<string, string, string> GetLocalBundlePath => null;\n"; } FileUtil.CreateClassFile( "HotUpdatePathData", rootNs + "." + constNs, Application.dataPath + "/" + rootNs + "/" + constNs, "Singleton<HotUpdatePathData>, IHotUpdatePath", "使用AB包管理资源时必须的定义路径的常量类", content, true, false, "using ReadyGamerOne.MemorySystem;\n" + "using UnityEngine;\n" + "using System.IO;\n" + "using System;\n" + "using System.Collections.Generic;\n" + "using ReadyGamerOne.Common;\n", otherClassBody: otherClassBody); }
/// <summary> /// 创建Panel类 /// </summary> /// <param name="panelPrefabDir"></param> /// <param name="viewNs"></param> /// <param name="constNs"></param> /// <param name="rootNs"></param> /// <param name="autoDirName"></param> /// <returns></returns> private static bool CreatePanelFile(string panelPrefabDir, string viewNs, string constNs, string rootNs, string autoDirName) { if (!Directory.Exists(panelPrefabDir)) { Debug.LogWarning("panelPrefabDir is not exist ! "); return(false); } foreach (var fullPath in Directory.GetFiles(panelPrefabDir)) { if (fullPath.EndsWith(".meta")) { continue; } if (!fullPath.EndsWith(".prefab")) { continue; } var prefabName = Path.GetFileNameWithoutExtension(fullPath); var fileName = prefabName; var className = prefabName; var safePartDir = Application.dataPath + "/" + rootNs + "/" + viewNs; var autoPartDir = safePartDir + "/" + autoDirName; // Debug.Log(safePartDir); FileUtil.CreateFolder(safePartDir); FileUtil.CreateFolder(autoPartDir); var safePartPath = safePartDir + "/" + fileName + ".cs"; var autoPartPath = autoPartDir + "/" + fileName + ".cs"; #region AutoPart if (File.Exists(autoPartPath)) { File.Delete(autoPartPath); } var stream = File.CreateText(autoPartPath); stream.Write("using ReadyGamerOne.View;\n" + "using " + rootNs + "." + constNs + ";\n" + "namespace " + rootNs + "." + viewNs + "\n" + "{\n" + "\tpublic partial class " + className + " : AbstractPanel\n" + "\t{\n" + "\t\tpartial void OnLoad();\n" + "\n" + "\t\tprotected override void Load()\n" + "\t\t{\n" + "\t\t\tCreate(PanelName." + className + ");\n" + "\t\t\tOnLoad();\n" + "\t\t}\n" + "\t}\n" + "}\n"); stream.Flush(); stream.Dispose(); stream.Close(); #endregion #region SafePart if (File.Exists(safePartPath)) { continue; } stream = new StreamWriter(safePartPath); stream.Write("namespace " + rootNs + "." + viewNs + "\n" + "{\n" + "\tpublic partial class " + fileName + "\n" + "\t{\n" + "\t\tpartial void OnLoad()\n" + "\t\t{\n" + "\t\t\t//do any thing you want\n" + "\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); #endregion } return(true); }
static void OnToolsGUI(string rootNs, string viewNs, string constNs, string dataNs, string autoDir, string scriptNs) { var resources = Application.dataPath + "/Resources"; EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.LabelField("自动常量文件生成目录", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir); EditorGUILayout.Space(); createPanelClasses = EditorGUILayout.Toggle("是否自动生成Panel类型", createPanelClasses); if (createPanelClasses) { EditorGUILayout.LabelField("自动生成Panel文件路径", Application.dataPath + "/" + rootNs + "/" + viewNs); } EditorGUILayout.Space(); createGameMgr = EditorGUILayout.Toggle("是否生成GameMgr", createGameMgr); if (createGameMgr) { EditorGUILayout.LabelField("自动生成" + rootNs + "Mgr文件路径", Application.dataPath + "/" + rootNs + "/" + scriptNs); } EditorGUILayout.Space(); if (GUILayout.Button("开启自动生成", GUILayout.Height(2 * EditorGUIUtility.singleLineHeight))) { autoClassName.Clear(); var rootDir = Application.dataPath + "/" + rootNs; FileUtil.CreateFolder(rootDir); FileUtil.CreateFolder(rootDir + "/" + constNs); FileUtil.CreateFolder(rootDir + "/" + constNs + "/" + autoDir); nameToPath.Clear(); if (!Directory.Exists(resources)) { Directory.CreateDirectory(resources); return; } FileUtil.SearchDirectory(resources, OprateFile, false, filPath => OprateDir(filPath, rootNs, constNs, autoDir) ); if (nameToPath.Count > 0) { FileUtil.CreateConstClassByDictionary("OtherResPath", rootDir + "/" + constNs + "/" + autoDir, rootNs + "." + constNs, nameToPath); FileUtil.CreateConstClassByDictionary("OtherResName", rootDir + "/" + constNs + "/" + autoDir, rootNs + "." + constNs, nameToPath.Select(pair => pair.Key).ToDictionary(name => name)); autoClassName.Add("OtherRes"); } if (autoClassName.Contains("Panel")) { CreatePanelFile(Application.dataPath + "/Resources/ClassPanel", viewNs, constNs, rootNs, autoDir); } if (createGameMgr) { CreateMgr(rootNs, constNs, scriptNs, autoDir); } AssetDatabase.Refresh(); Debug.Log("生成结束"); } EditorGUILayout.Space(); if (autoClassName.Count > 0) { var str = "生成的类型有:"; foreach (var name in autoClassName) { str += "\n" + name + "Name" + "\t\t" + name + "Path"; } EditorGUILayout.HelpBox(str, MessageType.Info); } }
/// <summary> /// 遍历Resources目录时,操作目录的函数 /// </summary> /// <param name="dirInfo"></param> /// <param name="rootNs"></param> /// <param name="constNs"></param> /// <param name="autoDir"></param> private static bool OprateDir(DirectoryInfo dirInfo, string rootNs, string constNs, string autoDir) { // Debug.Log(dirInfo.FullName); var dirName = dirInfo.FullName.GetAfterLastChar('\\'); if (dirName == "Resources") { return(true); } // Debug.Log("operateDir: " + dirName); if (dirName.StartsWith("Global")) { return(false); } if (dirName.StartsWith("Class")) { autoClassName.Add(dirName.GetAfterSubstring("Class")); if (createPathFile) { FileUtil.ReCreateFileNameConstClassFromDir( dirName.GetAfterSubstring("Class") + "Path", Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir, dirInfo.FullName, rootNs + "." + constNs, (fileInfo, stream) => { if (!fileInfo.FullName.EndsWith(".meta")) { var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName); var varName = FileUtil.FileNameToVarName(fileName); var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\") .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName)); stream.Write("\t\tpublic const string " + varName + " = @\"" + loadPath + "\";\n"); } }, true); } var className = dirName.GetAfterSubstring("Class") + "Name"; FileUtil.ReCreateFileNameConstClassFromDir( className, Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir, dirInfo.FullName, rootNs + "." + constNs, (fileInfo, stream) => { if (!fileInfo.FullName.EndsWith(".meta")) { var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName); var varName = FileUtil.FileNameToVarName(fileName); var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\") .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName)); if (allResPathDic.ContainsKey(varName)) { Debug.LogWarning("出现同名资源文件:" + fileInfo); } else { allResPathDic.Add(varName, loadPath); allResFileNameDic.Add(varName, fileName); } stream.Write("\t\tpublic const string " + varName + " = @\"" + fileName + "\";\n"); } }, true); } else { FileUtil.SearchDirectory(dirInfo.FullName, OprateFile, true); } return(true); }
/// <summary> /// 创建GameMgr /// </summary> /// <param name="rootNs"></param> /// <param name="constNs"></param> /// <param name="scriptNs"></param> /// <param name="autoDirName"></param> private static void CreateMgr(string rootNs, string constNs, string scriptNs, string autoDirName) { var safePartDir = Application.dataPath + "/" + rootNs + "/" + scriptNs; var autoPartDir = safePartDir + "/" + autoDirName; FileUtil.CreateFolder(safePartDir); FileUtil.CreateFolder(autoPartDir); var fileName = rootNs + "Mgr"; var safePartPath = safePartDir + "/" + fileName + ".cs"; var autoPartPath = autoPartDir + "/" + fileName + ".cs"; #region AutoPart if (File.Exists(autoPartPath)) { File.Delete(autoPartPath); } var stream = new StreamWriter(autoPartPath); stream.Write("using ReadyGamerOne.Script;\n"); if (_resourceManagerType == ResourceManagerType.AssetBundle) { stream.Write("using " + rootNs + "." + constNs + ";\n"); } stream.Write("using ReadyGamerOne.MemorySystem;\n"); stream.Write("namespace " + rootNs + "." + scriptNs + "\n" + "{\n" + "\tpublic partial class " + fileName + " : AbstractGameMgr<" + fileName + ">\n" + "\t{\n"); switch (_resourceManagerType) { case ResourceManagerType.Resource: stream.Write("\t\tprotected override IResourceLoader ResourceLoader => ResourcesResourceLoader.Instance;\n" + "\t\tprotected override IAssetConstUtil AssetConstUtil => Utility.AssetConstUtil.Instance;\n"); break; case ResourceManagerType.AssetBundle: stream.Write("\t\tprotected override IResourceLoader ResourceLoader => AssetBundleResourceLoader.Instance;\n" + "\t\tprotected override IHotUpdatePath PathData => HotUpdatePathData.Instance;\n" + "\t\tprotected override IOriginAssetBundleUtil OriginBundleData => OriginBundleUtil.Instance;\n"); break; } stream.Write("\t\tpartial void OnSafeAwake();\n"); stream.Write("\t\tprotected override void Awake()\n" + "\t\t{\n" + "\t\t\tbase.Awake();\n" + "\t\t\tOnSafeAwake();\n"); stream.Write("\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); #endregion #region SafePart if (File.Exists(safePartPath)) { return; } stream = new StreamWriter(safePartPath); var usePanel = autoClassName.Contains("Panel"); var useAudio = autoClassName.Contains("Audio"); if (useAudio || usePanel) { stream.Write("using ReadyGamerOne.EditorExtension;\n" + "using " + rootNs + "." + constNs + ";\n" + "using ReadyGamerOne.Script;\n"); } if (usePanel) { stream.Write("using ReadyGamerOne.View;\n"); } stream.Write("namespace " + rootNs + "." + scriptNs + "\n" + "{\n" + "\tpublic partial class " + fileName + "\n" + "\t{\n"); if (usePanel) { stream.Write("\t\tpublic StringChooser startPanel = new StringChooser(typeof(PanelName));\n"); } if (useAudio) { stream.Write("\t\tpublic StringChooser startBgm = new StringChooser(typeof(AudioName));\n"); } stream.Write("\t\tpartial void OnSafeAwake()\n" + "\t\t{\n"); if (usePanel) { stream.Write("\t\t\tPanelMgr.PushPanel(startPanel.StringValue);\n"); } if (useAudio) { stream.Write("\t\t\tAudioMgr.Instance.PlayBgm(startBgm.StringValue);\n"); } stream.Write("\t\t\t//do any thing you want\n" + "\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); #endregion }
/// <summary> /// 创建GameMgr /// </summary> /// <param name="rootNs"></param> /// <param name="constNs"></param> /// <param name="scriptNs"></param> /// <param name="autoDirName"></param> private static void CreateMgr(string rootNs, string constNs, string scriptNs, string autoDirName) { var safePartDir = Application.dataPath + "/" + rootNs + "/" + scriptNs; var autoPartDir = safePartDir + "/" + autoDirName; FileUtil.CreateFolder(safePartDir); FileUtil.CreateFolder(autoPartDir); var fileName = rootNs + "Mgr"; var safePartPath = safePartDir + "/" + fileName + ".cs"; var autoPartPath = autoPartDir + "/" + fileName + ".cs"; #region AutoPart if (File.Exists(autoPartPath)) { File.Delete(autoPartPath); } var stream = new StreamWriter(autoPartPath); stream.Write("using ReadyGamerOne.Script;\n"); stream.Write("namespace " + rootNs + "." + scriptNs + "\n" + "{\n" + "\tpublic partial class " + fileName + " : AbstractGameMgr<" + fileName + ">\n" + "\t{\n"); stream.Write("\t\tpartial void OnSafeAwake();\n"); stream.Write("\t\tprotected override void Awake()\n" + "\t\t{\n" + "\t\t\tbase.Awake();\n" + "\t\t\tOnSafeAwake();\n"); stream.Write("\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); #endregion #region SafePart if (File.Exists(safePartPath)) { return; } stream = new StreamWriter(safePartPath); var usePanel = autoClassName.Contains("Panel"); var useAudio = autoClassName.Contains("Audio"); if (useAudio || usePanel) { stream.Write("using ReadyGamerOne.EditorExtension;\n"); stream.Write("using " + rootNs + "." + constNs + ";\n"); } if (usePanel) { stream.Write("using ReadyGamerOne.View;\n" + "using ReadyGamerOne.Script;\n"); } stream.Write("namespace " + rootNs + "." + scriptNs + "\n" + "{\n" + "\tpublic partial class " + fileName + "\n" + "\t{\n"); if (usePanel) { stream.Write("\t\tpublic StringChooser startPanel = new StringChooser(typeof(PanelName));\n"); } if (useAudio) { stream.Write("\t\tpublic StringChooser startBgm = new StringChooser(typeof(AudioPath));\n"); } stream.Write("\t\tpartial void OnSafeAwake()\n" + "\t\t{\n"); if (usePanel) { stream.Write("\t\t\tPanelMgr.PushPanel(startPanel.StringValue);\n"); } if (useAudio) { stream.Write("\t\t\tAudioMgr.Instance.PlayBgm(startBgm.StringValue);\n"); } stream.Write("\t\t\t//do any thing you want\n" + "\t\t}\n" + "\t}\n" + "}\n"); stream.Dispose(); stream.Close(); #endregion }