读一读

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]