预制体重构项目升级教程及注意事项
背景
022版本需求,预制体中的脚本,材质实例和UI资源不会在单独存放在Prefab文件夹中,而是直接引用项目中的脚本,材质实例和UI资源
之前版本:
Project的中的Prefabs文件夹中会单独存放预制体有关的资源
022版本:
Prefab的资源会和项目中的资源共用,即每个预制体中不会再有Materials,UI和Scripts文件夹
影响
工程内容中的预制体栏下不再显示除预制体以外的其他资源(引用关系还是在的)
升级脚本功能介绍
- 会将旧预制体(即Prefab和TempPrefabs文件夹中)中的脚本资源放到工程中的JavaScript目录下
- 会将旧预制体(即Prefab和TempPrefabs文件夹中)中的ui资源放到工程中的UI目录下
- 会将旧预制体(即Prefab和TempPrefabs文件夹中)中的材质实例资源放到工程中的Materials目录下
- 修改预制体中脚本的import信息,保证工程在打开时不会出现因导包路径不正确的编译报错(开发者在使用prefab时import的脚本可能是预制体外的脚本或者工程中的脚本import了预制体中的脚本,移动了ts脚本的位置,import的路径可能就不正确了)
- 修正ts.meta和ui.meta文件中Name字段与文件名不一致问题(不修正的话发布的时候会将这些文件给过滤掉)
刷脚本教程
将脚本文件或exe程序放在项目工程文件夹下
打开命令行输入 python UpdatePrefabAsset.py
等待程序跑完即可
pyimport os os.system("pip install chardet") os.system("pip install json") import re import shutil import chardet import json Prefabs = "Prefabs" TempPrefabs = "TempPrefabs" AssetTypeArr = ["Materials", "JavaScripts", "UI"] def get_file_encoding(path): with open(path,"rb") as f: data = f.read() file_encoding = chardet.detect(data).get('encoding') return file_encoding def fileMove(PrefabName: str, PrefabDir: str, DestDir: str) -> bool: if DestDir == "JavaScripts": SouPrefabDir = os.path.join(PrefabDir, "Script") else: SouPrefabDir = os.path.join(PrefabDir, DestDir) if not os.path.exists(SouPrefabDir) or len(os.listdir(SouPrefabDir)) == 0: return True # 在对应资源目录下新建Prefabs目录 DestPrefabsDir = os.path.join(DestDir, Prefabs, PrefabName) if "JavaScripts" in DestPrefabsDir: DestPrefabsDir = os.path.join(DestPrefabsDir,"Script") if not os.path.exists(DestPrefabsDir): os.makedirs(DestPrefabsDir) if os.path.exists(SouPrefabDir): filelist = os.listdir(SouPrefabDir) if DestDir == "UI": shutil.move(SouPrefabDir, DestPrefabsDir) else: for filename in filelist: if os.path.exists(os.path.join(DestPrefabsDir, filename)): print(f"Move Type {DestDir} exist {filename}" ) return False shutil.move(os.path.join(SouPrefabDir, filename), DestPrefabsDir) shutil.rmtree(SouPrefabDir) return True def UpdatePrefab(TypePrefab: str) -> bool: IsUpdateSuccess = False if not os.path.exists(TypePrefab) or not os.path.isdir(TypePrefab): print(f"Update {TypePrefab} Error! Folder does not exist!") return IsUpdateSuccess PrefabNameList = os.listdir(TypePrefab) for PrefabName in PrefabNameList: PrefabPath = os.path.join(TypePrefab, PrefabName) if os.path.isdir(PrefabPath): JSPath = os.path.join(PrefabPath, "Script") MatPath = os.path.join(PrefabPath, "Materials") UIPath = os.path.join(PrefabPath, "UI") if not (os.path.exists(JSPath) or os.path.exists(MatPath) or os.path.exists(UIPath)): PrefabPathList = os.listdir(PrefabPath) for PrefabPathFile in PrefabPathList: if PrefabPathFile.endswith('.prefab.meta') or PrefabPathFile.endswith('.prefab'): continue if os.path.isdir(os.path.join(PrefabPath, PrefabPathFile)): shutil.rmtree(os.path.join(PrefabPath, PrefabPathFile)) else: os.remove(os.path.join(PrefabPath, PrefabPathFile)) print(f"Update {PrefabPath} Prefab is the latest format " ) continue if os.path.exists(PrefabPath) and os.path.isdir(PrefabPath): IsUpdateSuccess = fileMove(PrefabName, PrefabPath, "JavaScripts") if not IsUpdateSuccess: print(f"Update {TypePrefab} JavaScripts Error!!!") return False IsUpdateSuccess = fileMove(PrefabName, PrefabPath, "Materials") if not IsUpdateSuccess: print(f"Update { TypePrefab} Materials Error!!!") return False IsUpdateSuccess = fileMove(PrefabName, PrefabPath, "UI") if not IsUpdateSuccess: print(f"Update {TypePrefab} UI Error!!!") return False print(f"Update {PrefabPath} succeeded") else: continue # 删除Prefab中除了.prefab文件和.prefab.meta文件的其它文件 InPrefabFileList = os.listdir(PrefabPath) for filename in InPrefabFileList: if not filename.endswith(".prefab") and not filename.endswith(".prefab.meta"): InPrefabFilePath = os.path.join(PrefabPath, filename) if os.path.isdir(InPrefabFilePath): shutil.rmtree(InPrefabFilePath) else: os.remove(InPrefabFilePath) return True def GetImportPath(ScriptContent): import_list = re.findall('import\\s*.*?\\s*from?\\s*(\\\'\\S+?\\\'|\\\"\\S+?\\\")', ScriptContent) return import_list def GetAllPrefabScriptMap(Path): ScriptMap = {} for root, dirs, files in os.walk(Path): for filename in files: if filename.endswith(".ts"): FilePath = os.path.join(root, filename) encoding_format= get_file_encoding(FilePath) with open(FilePath,"r",encoding=encoding_format) as f: ScriptContent = f.read() import_list = GetImportPath(ScriptContent) if import_list: import_list = [current_string.replace('"', '').replace("'","") for current_string in GetImportPath(ScriptContent)] ScriptMap[FilePath] = import_list return ScriptMap def ConcatPath(scriptPath, importPath): back_level = importPath.count('../') for i in range(back_level + 1): scriptPath = os.path.dirname(scriptPath) currentImportPath = os.path.join(scriptPath, DealImportPath(importPath)) return currentImportPath def GetAllScriptsPath(currentPath): AllScriptspath = [] for root, dirs, files in os.walk(currentPath): for filename in files: if filename.endswith(".ts"): AllScriptspath.append(os.path.join(root, filename)) return AllScriptspath def DealImportPath(importInfo): result = importInfo.replace('../', '').replace('/', '\\') + '.ts' return result def CheckImportPath(ScriptMap, movedScriptPathList): pass_list = ['ue', 'puerts', 'module_gm', 'module_guide', 'module_ranking', 'odin'] IssueScriptMap = {} for script in ScriptMap: importInfoDict = {} for importInfo in ScriptMap[script]: concatpath = ConcatPath(script, importInfo) if not os.path.exists(concatpath) and importInfo not in pass_list: for movedScriptPath in movedScriptPathList: if DealImportPath(importInfo).lower() in movedScriptPath.lower(): importInfoDict[importInfo] = concatpath.count('\\') - movedScriptPath.count('\\') if importInfoDict: IssueScriptMap[script] = importInfoDict return IssueScriptMap def ChangeImportInfo(IssueScriptMap): print(IssueScriptMap) for tsFilePath in IssueScriptMap: print(tsFilePath) print(IssueScriptMap[tsFilePath]) encoding_format= get_file_encoding(tsFilePath) with open(tsFilePath, "r", encoding=encoding_format) as f: ScriptContent = f.read() for importInfo in IssueScriptMap[tsFilePath]: newImportInfo = importInfo for i in range(abs(IssueScriptMap[tsFilePath][importInfo])): if IssueScriptMap[tsFilePath][importInfo] > 0: newImportInfo = '../' + newImportInfo else: newImportInfo = newImportInfo.replace('../','',1) if '../' not in newImportInfo: newImportInfo = './' + newImportInfo print(importInfo, newImportInfo) if '"'+importInfo+'"' in ScriptContent: ScriptContent = ScriptContent.replace('"'+importInfo+'"', '"'+newImportInfo+'"') else: ScriptContent = ScriptContent.replace("'"+importInfo+"'", "'"+newImportInfo+"'") with open(tsFilePath, 'w', encoding=encoding_format) as f: f.write(ScriptContent) def FindAllUIPathInProject(ScriptContent): uiPathList = re.findall("\'/Prefabs.*\.ui\'|\"/Prefabs.*\.ui\"", ScriptContent) uiPathList = [uiPath.replace('"', '').replace("'","") for uiPath in uiPathList] return uiPathList def ChangesScriptUIPath(tsFilePath): for root, dirs, files in os.walk(tsFilePath): for filename in files: if filename.endswith(".ts"): FilePath = os.path.join(root, filename) encoding_format= get_file_encoding(FilePath) with open(FilePath, "r", encoding=encoding_format) as f: ScriptContent = f.read() for uiPath in FindAllUIPathInProject(ScriptContent): print(uiPath, '/UI' + uiPath) ScriptContent = ScriptContent.replace(uiPath, '/UI' + uiPath) with open(FilePath, 'w', encoding=encoding_format) as f: f.write(ScriptContent) def compare_fileName_metaName(Path:str): if os.path.exists(Path) and os.path.isdir(Path): FileList = os.listdir(Path) for FileName in FileList: curPath = os.path.join(Path,FileName) if os.path.isdir(curPath): compare_fileName_metaName(curPath) elif curPath.endswith(".ts.meta") or curPath.endswith(".ui.meta"): encoding_format= get_file_encoding(curPath) try: if os.path.exists(curPath): with open(curPath,'r',encoding=encoding_format) as f: metaData = json.load(f) metaName = metaData["Name"] fileBaseName = os.path.basename(curPath).split(".meta")[0] if metaName != fileBaseName : print("Modify Meta Name ->>> file path:{curPath}") metaData["Name"] = fileBaseName with open(curPath,'w',encoding=encoding_format) as w: json.dump(metaData,w) except json.decoder.JSONDecodeError: print(f"Error {curPath}") if __name__ == '__main__': compare_fileName_metaName(Prefabs) compare_fileName_metaName(TempPrefabs) currentProjectPath = os.getcwd() JavaScriptPath = os.path.join(currentProjectPath, AssetTypeArr[1]) IsUpdateSuccess = False IsUpdateSuccess = UpdatePrefab(TempPrefabs) if IsUpdateSuccess: print("TempPrefabs Asset Update Success!!!") IsUpdateSuccess = UpdatePrefab(Prefabs) if IsUpdateSuccess: print("Prefabs Asset Update Success!!!") movedScriptPathList = GetAllScriptsPath(JavaScriptPath) ScriptMap = GetAllPrefabScriptMap(JavaScriptPath) print('============================================') issueScriptMap = CheckImportPath(ScriptMap, movedScriptPathList) ChangeImportInfo(issueScriptMap) ChangesScriptUIPath(JavaScriptPath)
import os os.system("pip install chardet") os.system("pip install json") import re import shutil import chardet import json Prefabs = "Prefabs" TempPrefabs = "TempPrefabs" AssetTypeArr = ["Materials", "JavaScripts", "UI"] def get_file_encoding(path): with open(path,"rb") as f: data = f.read() file_encoding = chardet.detect(data).get('encoding') return file_encoding def fileMove(PrefabName: str, PrefabDir: str, DestDir: str) -> bool: if DestDir == "JavaScripts": SouPrefabDir = os.path.join(PrefabDir, "Script") else: SouPrefabDir = os.path.join(PrefabDir, DestDir) if not os.path.exists(SouPrefabDir) or len(os.listdir(SouPrefabDir)) == 0: return True # 在对应资源目录下新建Prefabs目录 DestPrefabsDir = os.path.join(DestDir, Prefabs, PrefabName) if "JavaScripts" in DestPrefabsDir: DestPrefabsDir = os.path.join(DestPrefabsDir,"Script") if not os.path.exists(DestPrefabsDir): os.makedirs(DestPrefabsDir) if os.path.exists(SouPrefabDir): filelist = os.listdir(SouPrefabDir) if DestDir == "UI": shutil.move(SouPrefabDir, DestPrefabsDir) else: for filename in filelist: if os.path.exists(os.path.join(DestPrefabsDir, filename)): print(f"Move Type {DestDir} exist {filename}" ) return False shutil.move(os.path.join(SouPrefabDir, filename), DestPrefabsDir) shutil.rmtree(SouPrefabDir) return True def UpdatePrefab(TypePrefab: str) -> bool: IsUpdateSuccess = False if not os.path.exists(TypePrefab) or not os.path.isdir(TypePrefab): print(f"Update {TypePrefab} Error! Folder does not exist!") return IsUpdateSuccess PrefabNameList = os.listdir(TypePrefab) for PrefabName in PrefabNameList: PrefabPath = os.path.join(TypePrefab, PrefabName) if os.path.isdir(PrefabPath): JSPath = os.path.join(PrefabPath, "Script") MatPath = os.path.join(PrefabPath, "Materials") UIPath = os.path.join(PrefabPath, "UI") if not (os.path.exists(JSPath) or os.path.exists(MatPath) or os.path.exists(UIPath)): PrefabPathList = os.listdir(PrefabPath) for PrefabPathFile in PrefabPathList: if PrefabPathFile.endswith('.prefab.meta') or PrefabPathFile.endswith('.prefab'): continue if os.path.isdir(os.path.join(PrefabPath, PrefabPathFile)): shutil.rmtree(os.path.join(PrefabPath, PrefabPathFile)) else: os.remove(os.path.join(PrefabPath, PrefabPathFile)) print(f"Update {PrefabPath} Prefab is the latest format " ) continue if os.path.exists(PrefabPath) and os.path.isdir(PrefabPath): IsUpdateSuccess = fileMove(PrefabName, PrefabPath, "JavaScripts") if not IsUpdateSuccess: print(f"Update {TypePrefab} JavaScripts Error!!!") return False IsUpdateSuccess = fileMove(PrefabName, PrefabPath, "Materials") if not IsUpdateSuccess: print(f"Update { TypePrefab} Materials Error!!!") return False IsUpdateSuccess = fileMove(PrefabName, PrefabPath, "UI") if not IsUpdateSuccess: print(f"Update {TypePrefab} UI Error!!!") return False print(f"Update {PrefabPath} succeeded") else: continue # 删除Prefab中除了.prefab文件和.prefab.meta文件的其它文件 InPrefabFileList = os.listdir(PrefabPath) for filename in InPrefabFileList: if not filename.endswith(".prefab") and not filename.endswith(".prefab.meta"): InPrefabFilePath = os.path.join(PrefabPath, filename) if os.path.isdir(InPrefabFilePath): shutil.rmtree(InPrefabFilePath) else: os.remove(InPrefabFilePath) return True def GetImportPath(ScriptContent): import_list = re.findall('import\\s*.*?\\s*from?\\s*(\\\'\\S+?\\\'|\\\"\\S+?\\\")', ScriptContent) return import_list def GetAllPrefabScriptMap(Path): ScriptMap = {} for root, dirs, files in os.walk(Path): for filename in files: if filename.endswith(".ts"): FilePath = os.path.join(root, filename) encoding_format= get_file_encoding(FilePath) with open(FilePath,"r",encoding=encoding_format) as f: ScriptContent = f.read() import_list = GetImportPath(ScriptContent) if import_list: import_list = [current_string.replace('"', '').replace("'","") for current_string in GetImportPath(ScriptContent)] ScriptMap[FilePath] = import_list return ScriptMap def ConcatPath(scriptPath, importPath): back_level = importPath.count('../') for i in range(back_level + 1): scriptPath = os.path.dirname(scriptPath) currentImportPath = os.path.join(scriptPath, DealImportPath(importPath)) return currentImportPath def GetAllScriptsPath(currentPath): AllScriptspath = [] for root, dirs, files in os.walk(currentPath): for filename in files: if filename.endswith(".ts"): AllScriptspath.append(os.path.join(root, filename)) return AllScriptspath def DealImportPath(importInfo): result = importInfo.replace('../', '').replace('/', '\\') + '.ts' return result def CheckImportPath(ScriptMap, movedScriptPathList): pass_list = ['ue', 'puerts', 'module_gm', 'module_guide', 'module_ranking', 'odin'] IssueScriptMap = {} for script in ScriptMap: importInfoDict = {} for importInfo in ScriptMap[script]: concatpath = ConcatPath(script, importInfo) if not os.path.exists(concatpath) and importInfo not in pass_list: for movedScriptPath in movedScriptPathList: if DealImportPath(importInfo).lower() in movedScriptPath.lower(): importInfoDict[importInfo] = concatpath.count('\\') - movedScriptPath.count('\\') if importInfoDict: IssueScriptMap[script] = importInfoDict return IssueScriptMap def ChangeImportInfo(IssueScriptMap): print(IssueScriptMap) for tsFilePath in IssueScriptMap: print(tsFilePath) print(IssueScriptMap[tsFilePath]) encoding_format= get_file_encoding(tsFilePath) with open(tsFilePath, "r", encoding=encoding_format) as f: ScriptContent = f.read() for importInfo in IssueScriptMap[tsFilePath]: newImportInfo = importInfo for i in range(abs(IssueScriptMap[tsFilePath][importInfo])): if IssueScriptMap[tsFilePath][importInfo] > 0: newImportInfo = '../' + newImportInfo else: newImportInfo = newImportInfo.replace('../','',1) if '../' not in newImportInfo: newImportInfo = './' + newImportInfo print(importInfo, newImportInfo) if '"'+importInfo+'"' in ScriptContent: ScriptContent = ScriptContent.replace('"'+importInfo+'"', '"'+newImportInfo+'"') else: ScriptContent = ScriptContent.replace("'"+importInfo+"'", "'"+newImportInfo+"'") with open(tsFilePath, 'w', encoding=encoding_format) as f: f.write(ScriptContent) def FindAllUIPathInProject(ScriptContent): uiPathList = re.findall("\'/Prefabs.*\.ui\'|\"/Prefabs.*\.ui\"", ScriptContent) uiPathList = [uiPath.replace('"', '').replace("'","") for uiPath in uiPathList] return uiPathList def ChangesScriptUIPath(tsFilePath): for root, dirs, files in os.walk(tsFilePath): for filename in files: if filename.endswith(".ts"): FilePath = os.path.join(root, filename) encoding_format= get_file_encoding(FilePath) with open(FilePath, "r", encoding=encoding_format) as f: ScriptContent = f.read() for uiPath in FindAllUIPathInProject(ScriptContent): print(uiPath, '/UI' + uiPath) ScriptContent = ScriptContent.replace(uiPath, '/UI' + uiPath) with open(FilePath, 'w', encoding=encoding_format) as f: f.write(ScriptContent) def compare_fileName_metaName(Path:str): if os.path.exists(Path) and os.path.isdir(Path): FileList = os.listdir(Path) for FileName in FileList: curPath = os.path.join(Path,FileName) if os.path.isdir(curPath): compare_fileName_metaName(curPath) elif curPath.endswith(".ts.meta") or curPath.endswith(".ui.meta"): encoding_format= get_file_encoding(curPath) try: if os.path.exists(curPath): with open(curPath,'r',encoding=encoding_format) as f: metaData = json.load(f) metaName = metaData["Name"] fileBaseName = os.path.basename(curPath).split(".meta")[0] if metaName != fileBaseName : print("Modify Meta Name ->>> file path:{curPath}") metaData["Name"] = fileBaseName with open(curPath,'w',encoding=encoding_format) as w: json.dump(metaData,w) except json.decoder.JSONDecodeError: print(f"Error {curPath}") if __name__ == '__main__': compare_fileName_metaName(Prefabs) compare_fileName_metaName(TempPrefabs) currentProjectPath = os.getcwd() JavaScriptPath = os.path.join(currentProjectPath, AssetTypeArr[1]) IsUpdateSuccess = False IsUpdateSuccess = UpdatePrefab(TempPrefabs) if IsUpdateSuccess: print("TempPrefabs Asset Update Success!!!") IsUpdateSuccess = UpdatePrefab(Prefabs) if IsUpdateSuccess: print("Prefabs Asset Update Success!!!") movedScriptPathList = GetAllScriptsPath(JavaScriptPath) ScriptMap = GetAllPrefabScriptMap(JavaScriptPath) print('============================================') issueScriptMap = CheckImportPath(ScriptMap, movedScriptPathList) ChangeImportInfo(issueScriptMap) ChangesScriptUIPath(JavaScriptPath)
也可以把这上边这个exe放在项目文件夹中双击执行升级项目
注意事项
脚本中如果的import路径和脚本文件的存放路径大小写不一致,在导出预制体时,该脚本文件依赖不会被导出。例:
脚本中的import信息为:import MGS from ../JavaScript/Module/MGS/MGs
脚本在PC中的存放路径为:JavaScript\Module\MGS\MGS.ts
MGS.ts 文件就不会被找到因为大小写不一致,请在书写import信息时保持正确的规范
预制体中脚本含有吸管自定义属性,在解除预制体时,吸管所吸附的guid不会随之改变,需要开发者自己重新捕获
工程内容中有文件夹且文件夹中有预制体并在场景中引用了,删除文件夹时,不会提供解除预制选项
导入旧的预制体到新的编辑器时,如果import的路径还是原来的,请务必根据新的路径,重新修改import信息
关于重复刷脚本问题
该脚本不支持重复刷,脚本逻辑中会检测如果项目下的JavaScript,Materials和UI文件夹中含有Prefabs文件时就会判定该项目已被升级过
脚本中也会出现报错字样
如果刷错了要怎么重新刷
- 建议刷之前备份出一个原工程
- 如果使用git回退请注意!!!
- git在回退时不会删除创建出来的文件夹
- 需要手动查看JavaScript,UI 和Materials文件夹中是否含有Prefabs文件夹
- 如果保留了目录结构,请手动删除JavaScript、UI 和Materials文件夹下的Prefabs文件夹(如果无法删除,请关闭编辑器)
如果有大量老的预制体需要升级,可以新建一个工程并执行升级脚本或exe程序,然后把生成出来的内容拷贝到正在开发的项目里,避免开发项目混乱