第一周 新项目熟悉 UGUI mvvm框架、UIText本地化、csv表格转为json文件、从谷歌服务器下载csv表工具制作
一、UGUI Mvvm框架
第一次接触mvvm框架,首先不管是什么,先看看这个东西是怎么用的,之后再来分析一下。
首先需要再 canvas 节点上放置 Root Model View,这个脚本会在运行时最先被实例出来。
然后再Default Context中添加你自己写的控制脚本,这个脚本将会控制所有这个Canvas上的所有UI。接下来看看这个 Context 脚本该怎么写。
//首先脚本需要继承 MonoBehaviourContext 这样才可以挂载到Default Contexts上面去
public class ModelCtrl : MonoBehaviourContext
{
#region Property Text
//这里定义属性 Property 这里的泛型 string 对应了绑定的UI上的属性
private readonly Property<string> _privateTextProperty = new Property<string>();
public string Text
{
get { return _privateTextProperty.GetValue(); }
set { _privateTextProperty.SetValue(value); }
}
#endregion
//这里做一个测试效果 就是按下A然后 改变text的内容
private void Update()
{
if (Input.GetKey(KeyCode.A))
{
Text = "测试";
}
}
}
然后接下来我们来创建一个text
接下来看看,我们这个Text的属性时如何改变UI显示的。
我们需要在UI上挂一个 UGUI Text Binding 脚本,然后再Path中填写,在ModelCtrl中的属性名称。这样Text Binding会自动去Canvas的RootModelView中寻找Default Context,然后找到path的属性与其绑定,这样只需要修改Text字符串即可,免去了查找物体,获取组件,修改这些步骤,使得ui和逻辑分离。
如果要给一个button添加点击事件,只需要在ModelCtrl中添加点击函数
void OnButtonClick()
{
Debug.Log("按钮被点击");
}
然后再button上添加onclick绑定脚本,在Path中添加函数名即可。
如果要设置一个UI的Active,只需要设置一个bool的Property即可。
#region Property TextActive
private readonly Property<bool> _privateTextActiveProperty = new Property<bool>();
public bool TextActive
{
get { return _privateTextActiveProperty.GetValue(); }
set { _privateTextActiveProperty.SetValue(value); }
}
#endregion
这样就可以通过设置这个bool值来控制UI的Active了。这里面还有很多其他属性的绑定。其功能会在以后的博客中慢慢演示。
关于mvvm框架,主要分为Model、View、ViewMode。
- Model: 代表数据模型,数据和业务逻辑都在Model中定义,
- View:代表UI试图,负责数据的显示;
- ViewModel :负责监听model中数据的改变并控制试图更新,处理用户交互操作。
MVVM框架的实现原理,和其他绑定组件的用法,后面博客再详细分析,这次先简单说一下用法让大家简单感受一下使用的感觉。
二、关于文字的本地化
MVVM框架中集成了文字本地化的相关功能。
在Format中用 $ $引起来的内容,是可以在代码中加载输出的。例如
之后我们需要在Awake里将TextBinding的中的 I18NModifier 委托注册一个你自己实现的函数,那么此时当这个Text要显示的时候,会将NickName传递到委托函数中,你返回的值就是 $ $中间的内容。这样就可以将各国的语言存放到json表中,在 $ $中放入key,委托函数里面通过key去寻找对应的value返回,这样就可以做到文字的本地化。 这里做一个简单的演示。
private void Awake()
{
TextBinding.I18NModifier += key => { return "별명"; };
}
private void Start()
{
Text = "abcd1234";
}
三、将CSV转换成json文件。
这里主要是因为,游戏中需要将多国的语言制作成csv表格,如果要做本地化,那么就要像之前说的,解析这些csv表格,转成json文件,然后再text中绑定key值,来寻找value实现文字的本地化功能。
这边工具还是比较简单的,直接上代码,需要注意的是再csv中,是用逗号作为分隔符,那么如果一个item中存在逗号的时候,这个item会被双引号引起来。如下图:
规律呢,就是一个item中引号的个数一定是双数的,如果你发现两个逗号中的元素引号是单数的,那么这个其中一定有一个逗号是,表中带有的,你需要拼接。
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
public class ToolsMune
{
[MenuItem("Tools/Csv2Json")]
static void CsvToJson()
{
//Debug.Log(Application.dataPath + "/csvTable.csv");
string[] scvline = File.ReadAllLines(Application.dataPath + "/csvTable.csv");
string[] Language = scvline[0].Split(',');
for (int i = 1; i < Language.Length; i++)
{
using (File.Create(Application.dataPath + "/Data/" + Language[i] + ".json")) ;
StringBuilder jsonstr = new StringBuilder("{");
bool falg = true;
for (int j = 1; j < scvline.Length; j++)
{
string[] block = scvline[j].Split(',');
int tempnum = 0;
string key = block[tempnum];
while (_isOddDoubleQuota(key))
{
key += block[++tempnum];
}
tempnum = i;
string value = block[tempnum];
while (_isOddDoubleQuota(value))
{
value += block[++tempnum];
}
if (key.StartsWith("\"") && key.EndsWith("\""))
key = key.Trim('"');
if (value.StartsWith("\"") && value.EndsWith("\""))
value = value.Trim('"');
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
continue;
if (falg)
{
falg = false;
jsonstr.AppendLine("\"" + key + "\" :" + "\"" + value + "\"");
}
else
{
jsonstr.AppendLine(",\"" + key + "\" :" + "\"" + value + "\"");
}
}
jsonstr.AppendLine("}");
File.WriteAllText(Application.dataPath + "/Data/" + Language[i] + ".json", jsonstr.ToString());
}
AssetDatabase.Refresh();
}
//引号个数
private static int _getDoubleQuotaCount(string str)
{
string[] strArray = str.Split('"');
int doubleQuotaCount = strArray.Length - 1;
doubleQuotaCount = doubleQuotaCount < 0 ? 0 : doubleQuotaCount;
return doubleQuotaCount;
}
//引号个数是否为双数
private static bool _isOddDoubleQuota(string str)
{
return _getDoubleQuotaCount(str) % 2 == 1;
}
}
四、从谷歌下载表格到本地
这里主要是因为项目组使用谷歌文档来实现共享文档办公,那么策划会将文件本地化的翻译文档放在Google,我们我们需要将其下载下来并且转换成josn,来实现文字本地化。
首先来演示一下我们需要一个什么样子的效果。
之后我们在Unity编辑器里面只需要输入dockey,即可获取等到文档的所有表格
然后我们选择需要下载的表格,点击Download就可以从谷歌文档下载到我们需要的csv表格。
接下来看看如何实现,首先来看看谷歌文档的url是什么样的。
可以看到,如果我们需要下载某个表格,需要知道两个东西,第一个是dockey,第二个是gid,gid表示你具体下载这个文档中的那个表格。
我们需要获取到有多少个表格,分别是哪些表格,每个表格对应的gid是什么,再看看表格的下载链接时什么样的。
好的,我们知道了这些之后,再来具体看看怎么做,这边我直接上代码。之后会把需要注意的地方解释一下。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
public class DownLoadCsvFromGoogle : EditorWindow
{
public static List<string> downloadToggleString = new List<string>();
public static List<bool> downloadToggleBool = new List<bool>();
public static List<string> gidList = new List<string>();
private static string googleDocKey = "";
public List<string> gid = new List<string>();
[MenuItem("Tools/DownloadCsv")]
static void Init()
{
DownLoadCsvFromGoogle window =
(DownLoadCsvFromGoogle)EditorWindow.GetWindow(typeof(DownLoadCsvFromGoogle), true, "SpreadSheet Downloader");
foreach (var VARIABLE in downloadToggleString)
{
downloadToggleBool.Add(false);
}
}
private void OnGUI()
{
EditorGUILayout.Separator();
googleDocKey = EditorGUILayout.TextField("GoogleDocKey", googleDocKey);
EditorGUILayout.Separator();
if (GUILayout.Button("Refresh SpreadSheet", GUILayout.ExpandWidth(true)))
{
if (string.IsNullOrEmpty(googleDocKey))
{
return;
}
GetSheetFormGoogle();
}
EditorGUILayout.Separator();
EditorGUILayout.BeginHorizontal();
if(GUILayout.Button("Select All",GUILayout.ExpandWidth(true)))
{
for (int i = 0; i < downloadToggleBool.Count; i++)
{
downloadToggleBool[i] = true;
}
}
if(GUILayout.Button("UnSelect All",GUILayout.ExpandWidth(true)))
{
for (int i = 0; i < downloadToggleBool.Count; i++)
{
downloadToggleBool[i] = false;
}
}
EditorGUILayout.EndHorizontal();
if (downloadToggleString.Count != 0)
{
for (int i = 0; i < downloadToggleString.Count; i++)
{
downloadToggleBool[i] = EditorGUILayout.Toggle(downloadToggleString[i], downloadToggleBool[i]);
}
}
EditorGUILayout.Separator();
if(GUILayout.Button("Download",GUILayout.ExpandWidth(true)))
{
downCsvToLocal();
}
}
void downCsvToLocal()
{
for (int i = 0; i < downloadToggleBool.Count; i++)
{
if (downloadToggleBool[i])
{
string url = "https://docs.google.com/spreadsheets/d/" + googleDocKey + "/export?format=csv&id=" +
googleDocKey + "&gid=" + gidList[i];
HttpWebRequest request = MakeWebRequest(url);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
using (Stream st = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(st,Encoding.UTF8))
{
string str = sr.ReadToEnd();
if (!Directory.Exists(Application.dataPath + "/CsvData/"))
{
Directory.CreateDirectory(Application.dataPath + "/CsvData/");
}
using (StreamWriter sw = File.CreateText(Application.dataPath + "/CsvData/" + downloadToggleString[i] + ".csv"))
{
sw.Write(str);
}
}
}
response.Close();
request.Abort();
}
}
AssetDatabase.Refresh();
}
void GetSheetFormGoogle()
{
string url = "https://docs.google.com/spreadsheets/d/" + googleDocKey;
System.Net.ServicePointManager.DefaultConnectionLimit = 50;
HttpWebRequest request = MakeWebRequest(url);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
string retString = string.Empty;
using (Stream st = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(st,Encoding.UTF8))
{
retString = sr.ReadToEnd();
}
}
response.Close();
request.Abort();
Debug.Log(retString);
Regex reg = new Regex("(?<=<div class=\"goog-inline-block docs-sheet-tab-caption\">)(.*?)(?=</div>)", RegexOptions.IgnoreCase);
MatchCollection ms = reg.Matches( retString );
downloadToggleString.Clear();
downloadToggleBool.Clear();
foreach (var VARIABLE in ms)
{
downloadToggleString.Add(VARIABLE.ToString());
downloadToggleBool.Add(false);
}
Getgid(retString);
}
public void Getgid( string resqstr )
{
gidList.Clear();
string[] strs = resqstr.Split(',');
int number = 0;
for (int i = 0; i < downloadToggleString.Count; i++)
{
for (int j = number; j < strs.Length; j++)
{
if (strs[j].Contains(downloadToggleString[i]))
{
number = j;
if(i != 0)
break;
}
}
gidList.Add(strs[number - 3].Replace("\\\"",""));
}
}
public HttpWebRequest MakeWebRequest(string url)
{
HttpWebRequest request = null;
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
request = WebRequest.Create(url) as HttpWebRequest;
request.ProtocolVersion = HttpVersion.Version11;
}
else
{
request = WebRequest.Create(url) as HttpWebRequest;
}
//由于是谷歌文档,所以需要开启代理选项,我这里没开。
//WebProxy proxy = new WebProxy("127.0.0.1",8001);
//request.Proxy = proxy;
request.Method = "GET";
request.KeepAlive = false;
request.AllowAutoRedirect = true;
return request;
}
private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
}
这里需要注意的几个点
- 首先在unity编辑器模式下,是没法使用www和UnityWebRequest,没法使用Unity中的携程,当然网上有大佬写出可以编辑器下使用协程的工具,我这里没有用,直接用 .NET 的HttpWebRequest,这里.NET提供了很多web方法,比如:WebRequest、HttpWebRequest、WebClient、HttpClient。这些web类的区别,我这里简单抄了一点别人的总结。
ebRequest/HttpWebRequest/HttpRequest/WebClient/HttpClient的区别
- WebRequest 的命名空间是: System.Net ,它是HttpWebRequest的抽象父类(还有其他子类如FileWebRequest ,FtpWebRequest),WebRequest的子类都用于从web获取资源。HttpWebRequest利用HTTP 协议和服务器交互,通常是通过 GET 和 POST 两种方式来对数据进行获取和提交。
- HttpRequest 类的命名空间是:System.Web,它是一个密封类,其作用是 让服务端读取客户端发送的请求,我们最熟悉的HttpRequest的实例应该是WebForm中Page类的属性Request了,我们可以轻松地从Request属性的QueryString,Form,Cookies集合获取数据中,也可以通过Request[“Key”]获取自定义数据。
- WebClient 命名空间是System.Net,WebClient很轻量级的访问Internet资源的类,在指定uri后可以发送和接受数据。WebClient提供了 DownLoadData,DownLoadFile,UploadData,UploadFile 方法,同时通过了这些方法对应的异步方法,通过WebClient我们可以很方便地上传和下载文件。
- HttpClient 是.NET4.5引入的一个HTTP客户端库,其命名空间为 System.Net.Http 。.NET 4.5之前我们可能使用WebClient和HttpWebRequest来达到相同目的。HttpClient利用了最新的面向任务模式,使得处理异步请求非常容易。
- 第二点是下载 谷歌文档可能需要代理,可以自己设置代理,不然会超时。
- 还有就是https 的 ssl问题了,我这边代码也有处理https的情况.
- 最后关于获取gid和表格列表,需要分析谷歌html页面,我们通过字符串截取来获取,这块代码里写的比较清楚了,如果有不清楚的,去试一下就明白了。