流星

流星消逝的时候,光明已在望。黑暗无论多么长,光明迟早总是会来的。

1.读取角色模型 skc文件 有更新!

    流星蝴蝶剑安装目录下的pmodel就是所有角色模型,骨骼以及自身动画,还有动作帧定义文件
    其中
    孟星魂 高模 :p0.skc 默认1500个面以内
    孟星魂 中模 :p0_800.skc 800面
    孟星魂 低模 :p0_300.skc 300面

    用文本方式打开skc文件,可以看到其首部 定义了一些材质
    类似:
    Material
    Texture nm001b01.tga
    ColorKey 0 0 0 0
    Ambient 1.000 1.000 1.000
    Diffuse 1.000 1.000 1.000
    Specular 1.000 1.000 1.000
    Emissive 0.000 0.000 0.000
    Opacity 1.000
    Option ALPHA
    TwoSide FALSE
    之后是定义了顶点
    类似:
    Vertices: 313
    v 2.63759 -1.76689 18.38704 vt 0.115 0.327 Bones 1 3 1.00000
    跟随顶点之后是三角面
    类似:
    Triangles: 296
    f 3 0 143 138 147
    接触过3D的,应该对这些都比较熟悉
    一般的一个组合mesh,通常在3d max里,会把使用不同材质的部位,作为子网格,也称为submesh
    文件开头定义的几个材质球,就是因为这个模型有多个子网格,每个使用不同的材质。
    这样其实加载这个网格不那么复杂。
    只要提取到了顶点列表,三角面列表。然后用一个meshfilter和一个meshrender,理论上就可以显示出来
    用unity5.4.1f1创建一个新工程,名称为 myMeteor
    然后新建一个读取skc文件的脚本 命名为SkcLoader
    在场景上新建一个空对象,放置在原点(0, 0, 0)处,把SkcLoader添加到这个空对象上
    把流星蝴蝶剑pmodel目录下的p0_300.skc改名为p0_300.skc.bytes放到Resources目录下
    同时把流星蝴蝶剑安装目录里的pTexture.pak解压缩后,把图片全部放到U3D myMeteor工程Resources目录下
    U3D目录格式为

    SkcLoader的代码为

    //代码开始
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System;
    
    public enum ParseError
    {
        None,
        Miss,
        ParseError,
    }
    
    public class MaterialUnit
    {
        public string Texture;
        public Color ColorKey;
        public Color Ambient;
        public Color Diffuse;
        public Color Specular;
        public Color Emissive;
        public float Opacity;
        public string Option;
        public bool TwoSide;
    }
    public class SkcLoader : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
            Load("p0_300.skc");
            MeshFilter mf = gameObject.AddComponent<MeshFilter>();
            MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
            mf.sharedMesh = mesh;
            mr.materials = Material();
    	}
    	
    	// Update is called once per frame
    	void Update () {
    	
    	}
        int StaticSkins = 0;
        int DynmaicSkins = 0;
        public string Skin;
        public MaterialUnit[] materials;
        public Mesh mesh;
        ParseError errorno = ParseError.None;
        public bool error { get { return errorno != ParseError.None; } }
        public void Load(string file)
        {
            TextAsset assets = Resources.Load<TextAsset>(file);
            if (assets == null)
            {
                errorno = ParseError.Miss;
                return;
            }
    
            try
            {
                string[] line = assets.text.Split(new char[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries);
                for (int i = 0; i < line.Length; i++)
                {
                    if (string.IsNullOrEmpty(line[i]))
                        continue;
                    if (line[i].StartsWith("#"))
                        continue;
                    string[] lineobj = line[i].Split(new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries);
                    if (lineobj[0] == "Static" && lineobj[1] == "Skins:")
                    {
                        StaticSkins = int.Parse(lineobj[2]);
                        int pos = i + 1;
                        for (int j = 0; j < StaticSkins; j++)
                        {
                            pos = ReadEach(line, pos, j);
                        }
                    }
                }
            }
            catch (Exception exp)
            {
                Debug.LogError(exp.StackTrace);
                errorno = ParseError.ParseError;
            }
            mesh.RecalculateBounds();
            mesh.RecalculateNormals();
        }
    
        int ReadEach(string[] line, int start, int idx)
        {
            int end = start;
            int left = 0;
            int matidx = -1;
            for (int i = start; i < line.Length; i++)
            {
                string[] lineobj = line[i].Split(new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries);
                if (lineobj[0].StartsWith("#"))
                    continue;
                else
                if (lineobj[0] == "Static" && lineobj[1] == "Skin" && lineobj.Length == 3)
                {
                    Skin = lineobj[2];
                }
                else if (lineobj[0] == "{")
                {
                    left++;
                }
                else if (lineobj[0] == "}")
                {
                    left--;
                    if (left == 0)
                        return i + 1;
                }
                else if (lineobj[0] == "Materials:")
                {
                    materials = new MaterialUnit[int.Parse(lineobj[1])];
                }
                else if (lineobj[0] == "Material")
                {
                    matidx++;
                    materials[matidx] = new MaterialUnit();
                }
                else if (lineobj[0] == "Texture")
                {
                    string text = lineobj[1];
                    int dot = text.LastIndexOf(".");
                    if (dot != -1)
                        text = text.Substring(0, dot);
                    materials[matidx].Texture = text;
                }
                else if (lineobj[0] == "ColorKey")
                {
                    materials[matidx].ColorKey = new Color(float.Parse(lineobj[1]), float.Parse(lineobj[2]), float.Parse(lineobj[3]), float.Parse(lineobj[4]));
                }
                else if (lineobj[0] == "Ambient")
                {
                    materials[matidx].Ambient = new Color(float.Parse(lineobj[1]), float.Parse(lineobj[2]), float.Parse(lineobj[3]));
                }
                else if (lineobj[0] == "Diffuse")
                {
                    materials[matidx].Diffuse = new Color(float.Parse(lineobj[1]), float.Parse(lineobj[2]), float.Parse(lineobj[3]));
                }
                else if (lineobj[0] == "Specular")
                {
                    materials[matidx].ColorKey = new Color(float.Parse(lineobj[1]), float.Parse(lineobj[2]), float.Parse(lineobj[3]));
                }
                else if (lineobj[0] == "Emissive")
                {
                    materials[matidx].ColorKey = new Color(float.Parse(lineobj[1]), float.Parse(lineobj[2]), float.Parse(lineobj[3]));
                }
                else if (lineobj[0] == "Opacity")
                {
                    materials[matidx].Opacity = float.Parse(lineobj[1]);
                }
                else if (lineobj[0] == "Option")
                {
                    materials[matidx].Option = lineobj[1];
                }
                else if (lineobj[0] == "TwoSide")
                {
                    materials[matidx].TwoSide = (lineobj[1].ToUpper() == "TRUE");
                }
                else if (lineobj[0] == "Vertices:")
                {
                    mesh = new Mesh();
                    mesh.name = Skin;
                    int count = int.Parse(lineobj[1]);
                    List<BoneWeight> boneWeight = new List<BoneWeight>();
                    List<Vector3> vec = new List<Vector3>();
                    List<Vector2> uv = new List<Vector2>();
                    for (int s = i + 1; s < i + 1 + count; s++)
                    {
                        string line2 = line[s];
                        string[] subline = line2.Split(new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries);
                        if (subline[0] == "v")
                        {
                            Vector3 v = new Vector3();
                            v.x = float.Parse(subline[1]);
                            v.z = float.Parse(subline[2]);
                            v.y = float.Parse(subline[3]);
    
                            Vector2 uvv = new Vector2();
                            uvv.x = float.Parse(subline[5]);
                            uvv.y = float.Parse(subline[6]);
                            BoneWeight weight = new BoneWeight();
                            int boneCtrlNum = int.Parse(subline[8]);
                            switch (boneCtrlNum)
                            {
                                case 1:
                                    weight.boneIndex0 = int.Parse(subline[9]);
                                    weight.weight0 = float.Parse(subline[10]);
                                    break;
                                case 2:
                                    weight.boneIndex0 = int.Parse(subline[9]);
                                    weight.weight0 = float.Parse(subline[10]);
                                    weight.boneIndex1 = int.Parse(subline[11]);
                                    weight.weight1 = float.Parse(subline[12]);
                                    break;
                                case 3:
                                    weight.boneIndex0 = int.Parse(subline[9]);
                                    weight.weight0 = float.Parse(subline[10]);
                                    weight.boneIndex1 = int.Parse(subline[11]);
                                    weight.weight1 = float.Parse(subline[12]);
                                    weight.boneIndex2 = int.Parse(subline[13]);
                                    weight.weight2 = float.Parse(subline[14]);
                                    break;
                                case 4:
                                    weight.boneIndex0 = int.Parse(subline[9]);
                                    weight.weight0 = float.Parse(subline[10]);
                                    weight.boneIndex1 = int.Parse(subline[11]);
                                    weight.weight1 = float.Parse(subline[12]);
                                    weight.boneIndex2 = int.Parse(subline[13]);
                                    weight.weight2 = float.Parse(subline[14]);
                                    weight.boneIndex3 = int.Parse(subline[15]);
                                    weight.weight3 = float.Parse(subline[16]);
                                    break;
                            }
                            boneWeight.Add(weight);
                            vec.Add(v);
                            uv.Add(uvv);
                        }
                    }
                    mesh.SetVertices(vec);
                    mesh.uv = uv.ToArray();
    
                    mesh.boneWeights = boneWeight.ToArray();
                    i += count;
                }
                else if (lineobj[0] == "Triangles:")
                {
                    int triNum = int.Parse(lineobj[1]);
                    Dictionary<int, Dictionary<int, int[]>> indic = new Dictionary<int, Dictionary<int, int[]>>();
                    for (int s = i + 1; s < i + 1 + triNum; s++)
                    {
                        string line2 = line[s];
                        int o = 0;
                        int p = 0;
                        int q = 0;
                        int matIdx2 = 0;
                        string[] subline = line2.Split(new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries);
                        if (subline[0] == "f")
                        {
                            matIdx2 = int.Parse(subline[2]);
                            o = int.Parse(subline[3]);
                            p = int.Parse(subline[4]);
                            q = int.Parse(subline[5]);
                            int[] ind = new int[3] { o, q, p };//鍙嶈浆yz
                            if (indic.ContainsKey(matIdx2))
                                indic[matIdx2].Add(s - i - 1, ind);
                            else
                            {
                                Dictionary<int, int[]> va = new Dictionary<int, int[]>();
                                va.Add(s - i - 1, ind);
                                indic.Add(matIdx2, va);
                            }
                        }
                    }
                    i += triNum;
                    mesh.subMeshCount = materials.Length;
                    for (int mm = 0; mm < materials.Length; mm++)
                    {
                        if (indic.ContainsKey(mm))
                        {
                            int cnt = indic[mm].Count;
                            int[] va = new int[cnt * 3];
                            int next = 0;
                            foreach (var each in indic[mm])
                            {
                                va[next++] = each.Value[0];
                                va[next++] = each.Value[1];
                                va[next++] = each.Value[2];
                            }
    
                            mesh.SetIndices(va, MeshTopology.Triangles, mm);
                            mesh.SetTriangles(va, mm);
                        }
                    }
    
                }
                else
                {
                    Debug.LogError(lineobj[0]);
                }
            }
            return line.Length - 1;
        }
    
        public Material[] Material()
        {
            Material[] ret = new Material[materials.Length];
            for (int i = 0; i < materials.Length; i++)
            {
                ret[i] = new Material(Shader.Find("Unlit/Texture"));
                ret[i].SetTexture("_MainTex", Resources.Load<Texture>(materials[i].Texture));
                ret[i].SetColor("_Color", materials[i].Diffuse);
            }
            return ret;
        }
    }
    

    //代码结束
    启动Editor,我们可以看到 孟星魂低模已经显示出来了
    孟星魂300面

    在这里需要注意几个位置
    1:原流星蝴蝶剑,按照我的想法,应该使用的是右手坐标系,而UNITY是左手坐标系的,所以 可以看到前面的脚本在读取 顶点,以及三角面的顺序时 做了一部分翻转,让其转换到了左手坐标系
    2:这个文件中顶点部分,多出来的骨骼权重,是因为这个模型是有骨骼以及动画的[本应该创建skinnedMeshRenderer],我们先显示出模型来,再一步步把骨骼以及动画都加进来
    3:材质,原来的材质是要支持透明度等设置的,我们先统一用自发光的 贴图材质(Unlit/Texture)来显示

    源工程

    #2017/10/8更新,流星支持5个骨骼控制一个顶点的权重,而unity只支持4个,所以如果有顶点受5个骨骼控制,那么得重新在3DMAX绑骨骼,并且设置蒙皮高级参数,最多20根骨骼控制一个顶点改成4个骨骼控制一个顶点

    validate