public IEnumerator CaptureScreenShow() { yield return new WaitForEndOfFrame(); string fileName = DateTime.Now.Ticks.ToString(); string tagerFolder = DateTime.Now.ToString("yyyy-MM-dd"); Texture2D t = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, true); t.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false); t.Apply(); byte[] byt = t.EncodeToPNG(); string SaveImagePath = Application.persistentDataPath.Substring(0, Application.persistentDataPath.IndexOf("Android")) + "/Pictures/Screenshots"; string pathName = Path.Combine( SaveImagePath , tagerFolder ); if (!Directory.Exists(pathName)) { Directory.CreateDirectory(pathName); } string path = pathName + "/" + fileName + ".png"; File.WriteAllBytes(path, byt); yield return null; //判断是否截图成功 if (File.Exists(path)) { #if UNITY_ANDROID //刷新相册 ScanFile(new string[] { pathName }); #endif } else { GlobalHallUIMgr.Instance.ShowSystemTipsUI(StringTable.GetString("givescore_cap_pic_err"), 1f, false); } } void ScanFile(string[] path) { using (AndroidJavaClass PlayerActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { AndroidJavaObject playerActivity = PlayerActivity.GetStatic<AndroidJavaObject>("currentActivity"); using (AndroidJavaObject Conn = new AndroidJavaObject("android.media.MediaScannerConnection", playerActivity, null)) { Conn.CallStatic("scanFile", playerActivity, path, null, null); } } }
截图成功后,如果只是保存到了某个目录上,手机是不会马上立刻的发现那张图片的,比如说,你要微信发送某张图片,如果手机都不知道有那张图片,自然就发送不到了,只能去指定目录分享了。
所以截图成功保存后,调用一下Android扫描文件API。
原理就是将使用同一套骨骼的模型部分,单独独立起来,制作成可以加载利用的资源。然后加载这些单独的部分,组合成模型。如果要替换某个部分,就将合成的部分资源换成其他就好了。
每个模型部分的骨骼数据要和原来的骨骼数据一致,这样就可以通过一个只有骨骼数据的GameObject,然后加上SkinnedMeshRenderer,用各个部分蒙皮成为一个千变万化的人物了。
因为使用的是骨骼动画,各部分的顶点和骨骼的绑定是预定好的,所有不管部分的变化,动画还是可以正常播放的。
using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(SkinnedMeshRenderer))] public class AvatarPipi : MonoBehaviour { //事先准备好的部件,美术做好的,可以做成prefab,需要时再通过名字来加载 public List<GameObject> eyes = new List<GameObject>(); public List<GameObject> faces = new List<GameObject>(); public List<GameObject> hairs = new List<GameObject>(); public List<GameObject> pants = new List<GameObject>(); public List<GameObject> shoes = new List<GameObject>(); public List<GameObject> tops = new List<GameObject>(); private List<CombineInstance> mList = new List<CombineInstance>();//子网格信息 private List<Transform> bones = new List<Transform>();//骨骼 private List<Material> mats = new List<Material>();//材质 private List<Transform> allBones = new List<Transform>(); void Start () { //获取所有的骨骼 allBones.AddRange(transform.GetComponentsInChildren<Transform>()); GeneraterAvatar(); } void GeneraterAvatar() { ChangeMeshType(eyes); ChangeMeshType(faces); ChangeMeshType(hairs); ChangeMeshType(pants); ChangeMeshType(shoes); ChangeMeshType(tops); SkinnedMeshRenderer my = this.GetComponent<SkinnedMeshRenderer>(); //整合所有信息到一个SkinnedMeshRenderer来渲染 Mesh m = new Mesh(); m.CombineMeshes(mList.ToArray(),false,false);//合并子网格 my.sharedMesh = m; my.materials = mats.ToArray(); my.bones = bones.ToArray(); } void ChangeMeshType(List<GameObject> type) { if (type.Count < 1) return; GameObject go = type[Random.Range(0, type.Count)]; SkinnedMeshRenderer skin = go.GetComponent<SkinnedMeshRenderer>(); if (skin == null) return; //获取部件的网格、材质、骨骼,聚合起来到列表中 CombineInstance combine = new CombineInstance(); combine.mesh = skin.sharedMesh; mList.Add(combine); mats.AddRange(skin.materials); GetBones(skin.bones); } void GetBones(Transform[] termB) { //遍历得到部位组件对应的骨骼 foreach (var bb in termB) { foreach (var b in allBones) { if (b.name.Equals(bb.name)) { bones.Add(b); } } } } void Update () { if (Input.GetKeyDown(KeyCode.Space)) { ResetAvatar(); GeneraterAvatar(); } } void ResetAvatar() { mList.Clear(); bones.Clear(); mats.Clear(); } }
代码就不分析了,注释都有的。记一下合并网格函数的用法:
Mesh.CombineMeshes(CombineInstance[] combine, bool mergeSubMeshes = true, bool useMatrices = true, bool hasLightmapData = false)
第一个参数就是要合并的网格们,网格的定义在combine.mesh;
第二个参数确定是否和合并网格到一个sub mesh中,不是的话各网格会独立开来,使用不同的材质。如果使用的是合并的材质,那么设置为true。
第三个参数确定是否使用CombineInstance.transform
private static StringBuilder ret = new StringBuilder(13); public static string FormateGoldString(string gold) { ret.Remove(0, ret.Length); int length = gold.Length; string result = string.Empty; if (length > 2) { ret.Append(gold.Substring(0, length - 2)); string str2 = gold.Substring(length - 2, 2); if (str2 != "00") { ret.Append("."); if (str2[1] == '0') { ret.Append(str2[0]); } else { ret.Append(str2); } } } else { if (length == 1) { if (gold == "0") { ret.Append(0); } else { ret.Append("0.0"); ret.Append(gold); } } else { ret.Append("0."); if (gold[1] == '0') { ret.Append(gold[0]); } else { ret.Append(gold); } } } return ret.ToString(); }
很简单的就是分析各种可能出现的情况,将小数部分提取出来,加个点连接起来。
UnityWebRequest web = UnityWebRequest.Get(url); yield return web.Send(); string message = web.downloadHandler.text;//获取到返回的文字信息
以前记得每种类型的资源都是有那么一个静态Handle去get获取下载的东西的,突然间想获取文字就忘记了
原来是直接使用UnityWebRequest实例里面的downloadHandler.text获取string,这个里面还有data是获取byte[]类型的
Texture2D是在UnityEngine命名空间中的,用来设置NGUI的UITexture的mainTexture属性显示图片,网络下载图片格式等。
一、保存为Png文件
web = UnityWebRequest.GetTexture(codeUrl); yield return web.Send(); Texture2D t = DownloadHandlerTexture.GetContent(web); web.Dispose(); string BaseCodePath = System.IO.Path.Combine(Application.persistentDataPath, "qrcode"); if (t != null) { byte[] bts = t.EncodeToPNG();//主要在 string PathName = BaseCodePath; if (!Directory.Exists(PathName)) { Directory.CreateDirectory(PathName); } File.WriteAllBytes(PathName + "/" + userId.ToString() + ".png", bts); }
二、加载图片为Texture2D
string BaseCodePath = System.IO.Path.Combine(Application.persistentDataPath, "qrcode"); string filePath = BaseCodePath + "/" + userId.ToString() + ".png"; if (File.Exists(filePath)) { byte[] bts = File.ReadAllBytes(filePath); Texture2D t = new Texture2D(500, 500);//宽和高 t.LoadImage(bts);//主要在这里 Code.mainTexture = t; }
public static string FormateGoldShow(UInt32 Gold) { double result = Math.Round((double)(1.0f *Gold / 100), 2); return (result.ToString()); }
不知道为什么(double的强转导致的),这种方法在电脑上是可以用的,但是到了手机上,精度就开始飘了,各种不准。
所以,我就用字符串解析的方法来格式化整数变小数用来显示了。(字符串解析方式(两位小数))
using System.Collections.Generic; using UnityEngine; using System.Xml; public class ConfigManager : Manager { private Dictionary<string, object> configs; public override void Init() { //获取到Resources/Config下的所有配置文件 并且解析 configs = new Dictionary<string, object>(); TextAsset[] textAsset = Resources.LoadAll<TextAsset>("Config"); for(int i =0;i<textAsset.Length;i++) { AnalysisConfig(textAsset[i]); Resources.UnloadAsset(textAsset[i]); } GameManager.Instance.DebugManager.Logic.Log("配置管理器初始化完成!"); } //加载解析xml配置 private bool AnalysisConfig(TextAsset file) { XmlDocument xml = new XmlDocument(); xml.LoadXml(file.text); XmlNode parent = xml.FirstChild; string FileKey = file.name; if (configs.ContainsKey(FileKey)){ GameManager.Instance.DebugManager.Error.Log("配置文件名不能重复!"); return false; } Dictionary<string, object> FileNode = new Dictionary<string, object>(); FileNode = ParseNode(parent) as Dictionary<string,object>; configs.Add(FileKey, FileNode); return true; } private object ParseNode(XmlNode node) { object newNode = new object(); XmlNodeList list = node.ChildNodes; if (list.Count > 0 && list[0].Name != "#text") { //数组 Dictionary<string, object> dic = new Dictionary<string, object>(); foreach (XmlNode cNode in list) { object ccNode = new object(); ccNode = ParseNode(cNode); if (dic.ContainsKey(cNode.Name)) { GameManager.Instance.DebugManager.Error.Log(string.Format("同一个父节点下的子节点的标签名不能相同:{0}", cNode.ParentNode.Name + cNode.Name)); return null; } dic.Add(cNode.Name, ccNode); } newNode = dic; } else { //单元素数据 退出点 newNode = node.InnerText; } return newNode; } /// <summary> /// 格式:配置文件名.根节点下的子节点.根节点下的子节点的子节点 /// 注意不需要写根节点的标签名 public T GetConfigByKey<T>(string key) { string[] keys = key.Split('.'); if (!configs.ContainsKey(keys[0])) { GameManager.Instance.DebugManager.Error.Log(string.Format("缺少{0}的配置文件", keys[0])); return default(T); } Dictionary<string, object> dic = configs[keys[0]] as Dictionary<string, object>; object TargetNode = dic; for (int i = 1; i < keys.Length; i++) { dic = TargetNode as Dictionary<string, object>;//往前推进 if (!dic.ContainsKey(keys[i])) { GameManager.Instance.DebugManager.Error.Log(string.Format("缺少{0}节点", keys[i])); return default(T); } TargetNode = dic[keys[i]]; } return (T)TargetNode; } }
启动的时候,将指定Resources/Config目录的配置文件一并解析到内存中,通过key的方式获取到配置信息
需要添加配置,只需要在Resources/Config目录下创建配置文件就行了,自己再根据格式获取到配置,用于一些不用经常修改的、轻量的配置
如果是一些需要经常修改,重量的,使用网络AssetBundle加载。
思路:来自于php laravel的配置
以前说过,可以控制Debug.unityLogger.logEnabled为false来全局控制输出,但是单单使用Debug.Log不好区分模块,也不好单独调试某一模块,一输出就全部输出了,很乱。
下一个就是自己封装Debug,用不同的方法表示不同模块,使用不同的开关控制模块输出,富文本输出不同的颜色区分,但是双击输出会定位到封装。
所以是封装成dll导入,Unity就不会定位到dll里面,而是上一层。但是,修改添加模块困难,所以这里封装成一个可扩展性的Debug管理器。
using UnityEngine; namespace DebugLog { public class MyDebug { public string Name; public string Color; public bool IsCanDebug = true; public MyDebug(string name, string color) { Name = name; Color = color; } public void Log(string mes) { if (IsCanDebug == false) return; string log = string.Format("<color={0}>{1}:</color>{2}", Color, Name, mes); Debug.Log(log); } } }
using System.Collections.Generic; namespace DebugLog { public class DebugManager { public MyDebug Default = new MyDebug("Default", "yellow"); public MyDebug UI = new MyDebug("UI","blue"); public MyDebug Net = new MyDebug("Net", "green"); public MyDebug Error = new MyDebug("Error", "red"); public MyDebug Logic = new MyDebug("Logic", "#FF6633"); private Dictionary<string, MyDebug> dic = new Dictionary<string, MyDebug>(); public DebugManager() { dic.Add(Default.Name, Default); dic.Add(UI.Name, UI); dic.Add(Net.Name, Net); dic.Add(Error.Name, Error); dic.Add(Logic.Name, Logic); } public void AddMyDebug(string name, string color) { if (!dic.ContainsKey(name)) { MyDebug debug = new MyDebug(name, color); dic.Add(name,debug); } } public MyDebug GetMyDebug(string name) { MyDebug debug = Default; if (dic.ContainsKey(name)) { debug = dic[name]; } return debug; } public void OpenAllDebug() { foreach (var i in dic) { i.Value.IsCanDebug = true; } } public void CloseAllDebug() { foreach (var i in dic) { i.Value.IsCanDebug = false; } } public void DebugOnly(string name) { if (!dic.ContainsKey(name)) return; CloseAllDebug(); dic[name].IsCanDebug = true; } } }
尽可能的把有的模块写成成员变量,方便使用时有提示,而不是使用Add后再用一个字符串的名字去获取这个Debug
实在是需要扩展,就可以直接Add再Get,也不需要重新修改导入dll了
方法lerp是一个根据t来计算插值的,如果已知起始到目标都为固定,如果t每次都几乎一样,那么效果就是一下子快然后慢下来。
所以,就要控制t的大小是由小到大的,提供一个speed速度的概念,从0往上加到最高速度,因为刚开始的差值大,很小的t也会造成计算出来的插值很大,所以前面要控制t的成长变慢,后面再快。
using UnityEngine; public class ZhuanPanMono : MonoBehaviour { public static bool ShowResult = false; public static uint Result = 0; private float speed = 0f; public static float z = 0; private float heightSpeed = 3f; private float scaleSpeed = 0.18f; private Transform go; // Use this for initialization void Start () { go = transform.GetChild(3); } // Update is called once per frame void Update () { if (!ShowResult) return; uint TargetRotation = Result * 45 + 10 * 360; z = Mathf.Lerp(z, TargetRotation, Time.deltaTime * speed); go.rotation = Quaternion.Euler(go.rotation.x,go.rotation.y,z); if (speed < heightSpeed) { //通过缩放来控制前期速度的增长过快 speed += Time.deltaTime * scaleSpeed; } if (scaleSpeed < 1 && speed > 1f) { //后期释放加速度的限制缩放 scaleSpeed += Time.deltaTime; } if (Mathf.Abs(z - TargetRotation) < 0.1f) { //转动结束 ShowResult = false; } } }
2D中,随机一个位置产生物体(如:Sprite),而产生的这个物体能够被看到,那么这个物体就必须要生成在可视的范围中。
这个范围怎么确定呢?通过视口空间转变为世界坐标就行了。(0,0)表示相机视口的最下方,(1,1)表示相机视口的最上方。
Vector3 up = Camera.main.ViewportToWorldPoint(new Vector3(1, 1, 0)); Vector3 down = Camera.main.ViewportToWorldPoint(Vector3.zero);
那么x的范围为[down.x,up.x]
y的范围为[down.y,up.y]