Unity游戏多语言工具包

        由于一开始的代码没有考虑多语言场景,导致代码中提示框和UI显示直接用了中文,最近开始提取代码的中文,提取起来太麻烦,所以拓展了之前的多语言包,降低了操作复杂度。最后把工具代码提取出来到单独项目里面,做一个分享,项目资源放到最后。

        接入方案和使用步骤

  1. 导入package
  2. 修改EnumMessageTxt文件内容,根据自己的需求新增或删除语言,如删除繁体中文,则删除第一行中的,繁体中文,删除第三行的繁体中文缩写|Cht,删除多余的繁体语言包内容(可以把下面所有行都删除),删除后内容为
    S,Key,简体中文
    //Area|
    1|Area|Cn
    //Common2~500|Common
    2|CommonTip|公共提示
    3|CommonTip2|公共提示2
    4|CommonTip3|公共提示
    5|CommonFormatTip|提示{0}
    //PanelA501~601|PanelA
    501|PanelABtn|按钮

    若要添加其他语言,务必要记得第三行中加入对应的缩写

  3. 点击拓展工具Tools/MessageDataTool/EnumMessageEditor,打开界面,点击加载按钮,选择文件进行修改,修改完毕点击生成代码按钮,等待生成完成,编译完成即可,加载代码在Panel脚本中,需要先初始化语言类型(缩写),再进行加载

  4. 语言包加载逻辑在MessageManager.InitCyData中,根据自己项目的资源加载方式进行修改

  5. 由于只是简单的分享, 所以工具可能缺少部分合法验证,如遇到报错等情况,可以查看堆栈信息,或者私信咨询

        逻辑介绍

        项目目录结构

        1.生成的Asset资源,繁体多语言数据

        2.生成的Asset资源,简体多语言数据,路径可以通过代码修改,测试需要,放在Resources下加载

        3.Demo场景

        4.Unity Console日志重定向脚本,方便根据日志跳转代码中文处

        5.匹配代码中文的脚本

        6.多语言工具窗口脚本

        7.Demo界面

        8.生成的多语言key脚本,枚举类型

        9.1,2中Asset资源对应的源Scriptable脚本

        10.多语言管理器,加载多语言内容

        11.多语言源txt文件,存放源内容

      编辑器拓展功能

        1.导入txt源文件,生成脚本,最初始的多语言工具拓展功能,没有删除,保留了下来,如果不使用预览工具时,可使用该功能

        2.多语言工具预览和操作界面

        3.匹配代码中文 功能

 多语言txt源文件

S,Key,简体中文,繁体中文
//Area|
1|Area|Cn|Cht
//Common2~500|Common
2|CommonTip|公共提示|公共提示繁体
3|CommonTip2|公共提示2|
4|CommonTip3|公共提示|
5|CommonFormatTip|提示{0}|
//PanelA501~601|PanelA
501|PanelABtn|按钮|按钮繁体

         1.首行格式固定为 S,Key,语言A,语言B,语言C 代码会根据该行后面的语言类型数量创建对应的数据

        2.第二行及后续所有以//开头的内容,表示为注释,不参与逻辑预算,主要功能是提示,以|分割,|后内容表示为标头,无逻辑意义,主要功能是方面添加Key,后面结合工具介绍

        3.除去首行及注释行,其他行内容均为多语言内容,格式为 枚举整型值|枚举名称|语言A文本|语言B文本...第三行这个枚举整型值为1的是特例,表示上面各个语言的缩写,也表示了生成的资源文件名称,不能重复。注意并不是1中有多少种语言,这里就要添加多少种语言文本,但是至少多语言A的要有值,如果语言文本没有定义,那么会取最前面的语言文本,也就是假如这里没有定义语言C的多内容语言,那么语言C对应的多语言内容会取语言A的。配合生成的枚举脚本和数据可以更直观理清,这里实例用了两种简体中文和繁体中文。

        生成的枚举脚本

namespace Message
{public enum EnumMessage{//Area|/// <summary>/// Cn/// </summary>Area = 1,//Common2~500|Common/// <summary>/// 公共提示/// </summary>CommonTip = 2,/// <summary>/// 公共提示2/// </summary>CommonTip2 = 3,/// <summary>/// 公共提示/// </summary>CommonTip3 = 4,/// <summary>/// 提示{0}/// </summary>CommonFormatTip = 5,//PanelA501~601|PanelA/// <summary>/// 按钮/// </summary>PanelABtn = 501,MaxNum,}
}

         生成的简体中文多语言资源内容,注意名称和上面源文件第三行的关系

        生成的繁体中文多语言资源内容,注意名称和上面源文件第三行的关系和空值填充 

 多语言工具预览和操作界面

        1. 选择的多语言txt源文件路径

        2.加载:拉起选择框,选择要加载的源文件。重载:依据1中的路径,重新刷新当前界面的数据。保存:保存界面数据到1中的路径。生成脚本:先保存到源文件,再根据当前界面的数据生成枚举脚本和Asset数据

        3.输入要搜索的多语言内容,内容为多语言A的内容

        4.粘贴+搜索:粘贴剪贴板内容到3的输入框,并进行搜索。搜索:搜索3的输入框内容。取消搜索:隐藏搜索结果

        5.搜索到的结果,可能有多条。内容从左到右依次为所在组名枚举整型值枚举名称

        6.根据当前条目生成获取多语言值的代码,代码内容为

Message.MessageManager.I.GetMessage(Message.EnumMessage.PanelABtn)

代码生成格式可以自行修改,逻辑在脚本文件MessageDataToolGenerateMessage函数中

        7.根据当前条目生成获取Format多语言值的代码,代码内容为

Message.MessageManager.I.GetMessageFormat(Message.EnumMessage.PanelABtn)

注意要配合format类型的多语言使用,代码生成格式可以修改,逻辑在脚本文件MessageDataToolGenerateFormatMessage函数中

        8.组列表,所有 // 开头的行,选中的为绿色高亮

        9.要添加的组名称,格式需要以//开头

        10.添加新组,组名为9中内容

        11.当前选中的组,可以在此处修改组名称

        12.修改组名称按钮,必须点击按钮才能修改,同时为了保证源文件的组顺序不变化,这里会自动保存文件

        13.组的标头,可以理解为当前组下所有内容的枚举名称前缀,方面增加前缀的,可以为空

        14.枚举整型值,全局唯一索引,可以手动修改,同一组内添加条目默认递增1,组内的第一个新建条目需要手动修改索引值为合适值

        15.枚举名称,全局唯一,可以手动修改

        16.给枚举名称添加标头前缀,保证格式统一

        17.多语言内容,在这里修改

        18.将当前剪贴板内容粘贴到17的输入框内

        20.同6

        21.同7

        22.删除当前条目,为了防止旧数据出错,删除条目不会修正索引,比如之前索引列表为2,3,4,删除了3之后,索引为4不会修正为3,而是留空

        23.添加新的条目,索引递增1,若13有标头,会自动给15输入框中赋值标头内容

        界面的操作的逻辑都在脚本里面,因为涉及到布局代码过多,这里不再解读,可以搜索按钮名称查看对应的逻辑

        匹配代码中文脚本

using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;/// <summary>
/// 匹配代码中的中文
/// </summary>
public class MatchCnInCode : Editor
{// 正则表达式匹配中文字符static Regex chineseCharRegex = new Regex("[\u4e00-\u9fa5]");//忽略的文件夹static string[] IgnoreDir = new string[] { @"/Message/", @"/ProtoBuffer/", @"/ThirdPlugins/" };//忽略的代码行static string[] IgnoreCode = new string[] { "[Header(", "Debugger.Log", "Debug.Log","#region" };//搜索的脚本路径static string searchScriptPath = "Assets/HotUpdate/Scripts";[MenuItem("Tools/MatchCnInCode")]static void Method(){if (EditorUtility.DisplayDialog("提示", "开始匹配代码中的中文", "确定", "取消")){Do();}}static void Do(){// 搜索所有的 .cs 文件var csFiles = AssetDatabase.FindAssets("t:script", new string[] { searchScriptPath });for (int i = 0; i < csFiles.Length; i++){var file = AssetDatabase.GUIDToAssetPath(csFiles[i]);bool hit = false;for (int j = 0; j < IgnoreDir.Length; j++){if (file.Contains(IgnoreDir[j])){hit = true;break;}}if (hit){continue;}var obj = AssetDatabase.LoadAssetAtPath<Object>(file);using (StreamReader sr = new StreamReader(file)){//匹配到中文bool matchCn = false;//在多行注释中bool inMulAnnotation = false;int lineIndex = 0;while (!sr.EndOfStream){lineIndex++;//要匹配的内容bool matched = false;var line = sr.ReadLine();if (string.IsNullOrEmpty(line)){continue;}//多行注释开始的索引int mulAnnotationStartIndex = line.IndexOf("/*");if (mulAnnotationStartIndex != -1){//在多行注释中inMulAnnotation = true;var indexBeforeStr = line.Substring(0, mulAnnotationStartIndex);var match = chineseCharRegex.Match(indexBeforeStr);if (match.Success){Debug.LogError($"{file}=>{lineIndex}=> {match.Value} => {line} ", obj);matched = true;}}//在多行注释中if (inMulAnnotation){//不含多行注释开始标识的匹配串string noAnnotationStartStr = string.Empty;//该行没有开始标识,直接找结束标识if (mulAnnotationStartIndex == -1){noAnnotationStartStr = line;}else//有开始标识,从开始标识后找结束标识防止/*/的情况{noAnnotationStartStr = line.Substring(mulAnnotationStartIndex + 2);}var mulAnnotationEndIndex = noAnnotationStartStr.IndexOf("*/");//有结尾符if (mulAnnotationEndIndex != -1){inMulAnnotation = false;var indexAfterStr = noAnnotationStartStr.Substring(mulAnnotationEndIndex);var match = chineseCharRegex.Match(indexAfterStr);if (match.Success){Debug.LogError($"{file}=>{lineIndex}=> {match.Value} => {line} ", obj);matched = true;}}}else{//查找是否有单行注释var singleAnnotationIndex = line.IndexOf("//");if (singleAnnotationIndex == -1){bool hitIgnore = false;for (int j = 0; j < IgnoreCode.Length; j++){var logIndex = line.IndexOf(IgnoreCode[j]);if (logIndex != -1){hitIgnore = true;}}if (!hitIgnore){var match = chineseCharRegex.Match(line);if (match.Success){Debug.LogError($"{file}=>{lineIndex}=> {match.Value} => {line} ", obj);matched = true;}}}else{var match = chineseCharRegex.Match(line.Substring(0, singleAnnotationIndex));if (match.Success){Debug.LogError($"{file}=>{lineIndex}=> {match.Value} => {line} ", obj);matched = true;}}}//匹配所有行,还是匹配到一行后返回//if (matched)//{//    break;//}}}}}
}

         主要逻辑就是根据正则匹配中文,同时要忽略掉注释以及特殊字符开头的行,注意上面的Debug.LogError输出格式为脚本路径=>行数=>匹配到的首字=>整行内容,下面日志重定向要用。这几处输出可以封装,这里省略了封装代码,下面是实际运行时的日志输出,注意这里的堆栈信息,也是日志重定向需要使用的

        日志重定向脚本

using System;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditorInternal;namespace TEngine.Editor
{/// <summary>/// 日志重定向相关的实用函数。/// </summary>internal static class LogRedirection{[OnOpenAsset(0)]private static bool OnOpenAsset(int instanceID, int line){if (line <= 0){return false;}// 获取资源路径string assetPath = AssetDatabase.GetAssetPath(instanceID);// 判断资源类型if (!assetPath.EndsWith(".cs")){return false;}var stackTrace = GetStackTrace();if (!string.IsNullOrEmpty(stackTrace) && stackTrace.Contains("MatchCnInCode.cs") && stackTrace.StartsWith("Assets")){//中文匹配的输出var arr = stackTrace.Split("=>");//0:路径 ,1: 行数var path = arr[0];var lineNum = int.Parse(arr[1]);var fullPath = UnityEngine.Application.dataPath.Substring(0, UnityEngine.Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));fullPath = $"{fullPath}{path}";// 跳转到目标代码的特定行InternalEditorUtility.OpenFileAtLineExternal(fullPath.Replace('/', '\\'), lineNum);return true;}return false;}/// <summary>/// 获取当前日志窗口选中的日志的堆栈信息。/// </summary>/// <returns>选中日志的堆栈信息实例。</returns>private static string GetStackTrace(){// 通过反射获取ConsoleWindow类var consoleWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");// 获取窗口实例var fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow",BindingFlags.Static |BindingFlags.NonPublic);if (fieldInfo != null){var consoleInstance = fieldInfo.GetValue(null);if (consoleInstance != null)if (EditorWindow.focusedWindow == (EditorWindow)consoleInstance){// 获取m_ActiveText成员fieldInfo = consoleWindowType.GetField("m_ActiveText",BindingFlags.Instance |BindingFlags.NonPublic);// 获取m_ActiveText的值if (fieldInfo != null){var activeText = fieldInfo.GetValue(consoleInstance).ToString();return activeText;}}}return null;}}
}

        主要逻辑在函数OnOpenAsset中,var stackTrace = GetStackTrace();这里获取了整个输出的所有信息,然后下一行代码判断信息中是否有“MatchCnInCode.cs”以及是否以“Assets”开头,如果满足这两个条件,我们需要对该日志输出进行重定向。再根据我们上面的输出格式脚本路径=>行数=>匹配到的首字=>整行内容,可以取到路径和行数,就可以实现点击该行输出,直接定位到日志中标记的行数了,注意这里的路径不能多空格,结尾的空格也不行,否则会无法定位到脚本编辑工具中,所以日志输出的时候要检查是否有多余的空格。

资源地址 

https://download.csdn.net/download/a598211757/90628215

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79826.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET MCP 文档

MCP 概述 MCP&#xff08;Model Context Protocol&#xff09;是由 Anthropic 推出的一种开放协议&#xff0c;类似 AI 的 USB-C 扩展坞&#xff0c;用于在大模型和数据源之间建立安全的通信&#xff08;授权&#xff09;&#xff0c;让 AI 应用能够安全地访问和操作本地或远程…

【Linux】vim配置----超详细

目录 一、插件管理器准备 二、目录准备 三、安装插件 一、插件管理器准备 Vim-plug 是一个Vim插件管理器&#xff0c;利用异步并行可以快速地安装、更新和卸载插件。它的安装和配置都非常简单&#xff0c;而且在操作过程中会给出很多易读的反馈信息&#xff0c;是一个自由、…

PHP实现图片自动添加水印效果

<?php // 设置原始图片路径和水印图片路径 $original_image original.jpg; $watermark_image watermark.png;// 创建图片资源 $original imagecreatefromjpeg($original_image); $watermark imagecreatefrompng($watermark_image);// 获取图片尺寸 $original_width im…

检查新接手LINUX服务器应用的部署情况和正在运行的服务

当接手一台新的 Linux 服务器时&#xff0c;第一要务就是摸清系统上已经安装部署了哪些应用和服务。 本文将以 CentOS7为例&#xff0c;详细介绍如何系统地排查已安装的应用和服务&#xff0c;包括它们的安装方式和安装位置。 1.查看系统基本信息 首先获取系统整体信息&…

使用注解方式整合ssm时,启动tomcat扫描不到resource下面的xxxmapper.xml问题,解决方法

解决org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xxx.mapper.方法 在Spring与Mybatis整合时&#xff0c;可能会遇到这样的报错 原因&#xff1a; 其原因为mapper路径的映射错误&#xff0c;表示在尝试执行某个 Mapper 接口的方法时…

C++11特性补充

目录 lambda表达式 定义 捕捉的方式 可变模板参数 递归函数方式展开参数包 数组展开参数包 移动构造和移动赋值 包装器 绑定bind 智能指针 RAII auto_ptr unique_ptr shared_ptr 循环引用 weak_ptr 补充 总结 特殊类的设计 不能被拷贝的类 只能在堆上创建…

My SQL 索引

核心目标&#xff1a; 理解 mysql 索引的工作原理、类型、优缺点&#xff0c;并掌握创建、管理和优化索引的方法&#xff0c;以显著提升数据库查询性能。 什么是索引&#xff1f; 索引是一种特殊的数据库结构&#xff0c;它包含表中一列或多列的值以及指向这些值所在物理行的指…

极狐GitLab 注册限制如何设置?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 注册限制 (BASIC SELF) 您可以对注册实施以下限制&#xff1a; 禁用新注册。新注册需要管理员批准。需要用户电子邮件确认。…

10.(vue3.x+vite)div实现tooltip功能(css实现)

1:效果截图 2:代码实现 <template><div><div class="tooltip" style="margin-top: 20%; margin-left: 20%; background-color: blueviolet; color: white;

Linux下 文件的查找、复制、移动和解压缩

1、在/var/log目录下创建一个hehe.log的文件&#xff0c;其文件内容是&#xff1a; myhostname ghl mydomain localdomain relayhost [smtp.qq.com]:587 smtp_use_tls yes smtp_sasl_auth_enable yes smtp_sasl_security_options noanonymous smtp_sasl_tls_security_opt…

Ubuntu 安装 Docker 教程(官方推荐方式)

✅ 步骤 1&#xff1a;卸载旧版本&#xff08;如果有&#xff09; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done---### ✅ 步骤 2&#xff1a;更新 APT 索引并安装依赖项bash sudo a…

计算机视觉与深度学习 | Transformer原理,公式,代码,应用

Transformer 详解 Transformer 是 Google 在 2017 年提出的基于自注意力机制的深度学习模型,彻底改变了序列建模的范式,解决了 RNN 和 LSTM 在长距离依赖和并行计算上的局限性。以下是其原理、公式、代码和应用的详细解析。 一、原理 核心架构 Transformer 由 编码器(Encod…

计算机基础 | 常见进制与单位简介 / 表示 / 描述

注&#xff1a;本文为 “进制与常见单位应用” 相关文章合辑。 原文为繁体&#xff0c;注意术语描述差异。 略作重排。 进制简介&#xff08;二进制、八进制、十进制、十六进制&#xff09; 发表于 2017-01-20 郑中胜 数字系统&#xff08;Numeral system&#xff09;&#…

门面模式与适配器模式

一、门面模式 门面模式&#xff1a;提供统一接口访问子系统接口 1、包含角色 外观系统对外的统一接口子系统类的集合&#xff1b;并不知道外观角色的存在&#xff0c;需要为了配合外观角色而做特殊处理或修改 2、举例 原本开关灯要分别操作各个房间的灯&#xff0c;现在设置总…

SpringBoot Actuator指标收集:Micrometer与Prometheus集成

文章目录 引言一、Spring Boot Actuator基础二、Micrometer简介与集成三、基本指标收集与配置四、自定义业务指标实现五、与Prometheus集成六、实战案例&#xff1a;API性能监控总结 引言 在现代微服务架构中&#xff0c;监控应用程序的健康状况和性能指标变得至关重要。Sprin…

【Android面试八股文】Android应用进程的启动流程【二】

应用进程 1.1 Android系统进程的启动过程&#xff1a; 1、init进程fork出Zygote进程后&#xff0c;Zygote进程会创建一个服务端socket&#xff0c;等待AMS发起socket请求。 同时&#xff0c;由Zygote进程fork出的SystemServer进程会启动各项系统服务&#xff0c;其中就包含了A…

基于Django的AI客服租车分析系统

基于Django的AI客服租车分析系统 【包含内容】 【一】项目提供完整源代码及详细注释 【二】系统设计思路与实现说明 【三】AI智能客服与用户交互指导手册 【技术栈】 ①&#xff1a;系统环境&#xff1a;Python 3.8&#xff0c;Django 4.2框架 ②&#xff1a;开发环境&a…

全同态加密医疗数据分析集python实现

目录 摘要一、前言二、全同态加密与医疗数据分析概述2.1 全同态加密(FHE)简介2.2 医疗数据分析需求三、数据生成与预处理四、系统架构与流程4.1 系统架构图五、核心数学公式六、异步任务调度与(可选)GPU 加速七、PyQt6 GUI 设计八、完整代码实现九、自查测试与总结十、展望…

linux 搭建 dvwa 渗透测试环境

linux 安装 dvwa 1、分为4个部分&#xff0c;搭建dvwa渗透测试环境2、安装centos 7.63、安装apache http server4、安装mysql5、安装php6、运行dvwa 1、分为4个部分&#xff0c;搭建dvwa渗透测试环境 本文基于centos 7.6 搭建 dvwa 渗透测试环境 安装一个linux系统安装apache…

stm32(gpio的四种输出)

其实GPIO这个片上外设的功能&#xff1a; 用于控制IO引脚。 CPU就如同大脑&#xff0c;而这些片上外设就如同四肢一样的关系 如图 —————————————————————————————— OK类比了以上 其实GPIO是有 八种工作模式的 这八种工作模式 因为GPIO是面向IO…