流星   安卓版

流星蝴蝶剑 1.07/9.07 手机版 安卓版

3.读取地图 有更新!

    流星里地图文件读取出来需要解析gmb文件和des文件,部分模型可能与gmc相关
    类似假设点击第一关 炎硫岛 就会触发如下逻辑
    读取sn14.des,为什么是这一个是因为meteor.res里指定的
    读取sn14.gmb
    des文件描述了这个关卡里的实体和场景物品列表[虚体]
    释义sn14.des
    SceneObjects 66 DummeyObjects 92 场景实体有66个 场景物品有92个
    Object Plane01 物品名称
    {
    Position: 42.823 13.902 -14.560 位置世界变换
    Quaternion: -1.000 0.000 0.000 0.000 旋转世界变换
    TextureAnimation: 0 0.000 0.000 UV动画速度
    Custom:自身属性
    {
    }
    }
    一般来说des文件里每一个实体的模型数据都在gmb文件里可以找到,而且坐标,旋转等无需设置,gmb里的坐标即为世界坐标系,已经跟你算好了
    gmb和des是很大关系的,有了世界变换矩阵,很容易就可以将在gmb里的顶点数据转换到模型空间,然后再在u3d里设置gameobject的世界变换矩阵

    (2种有什么区别?)
    解释一下,一种是一个gameObject,放在原点,然后添加MeshFilter组件,组件上的Mesh顶点数据,与gmb文件里一样,但是这种时候,一旦此物体绕Y轴旋转,那么就是绕原点过Y轴转动
    如果是一个gameObject设置为des文件里描述的坐标,那么其就可以绕自身的轴心过Y轴转动,不同的是他的MeshFilter里的Mesh顶点数据,需要通过gmb里的顶点,通过世界变换矩阵的逆矩阵算出来

    注意看右上角,坐标

    注意看右上角,坐标

    场景物品,指的是,属性箱子,武器箱子,酒坛,板凳等一些场景上与玩家发生交互的动态物品,这部分数据一般在cmodel下有其对应的des文件描述

    要用U3D里显示一个地图,最好的办法是在编辑状态下调用加载地图函数,然后贴图或者其他不正确的地方,由于地图已经在编辑状态下的场景中,所以我们可以随便调整
    还是使用上次创建的工程
    要加载地图,数据是必不可少的,类似des,gmb还有地图上模型贴图,都要放到Resources目录下。
    其次,des文件和gmc文件以及gmc文件,都需要在后缀上加入.bytes,这个是u3d读取自定义数据的方法,如果不清楚可以下载源工程查看目录结构

    新建一个编辑器类 LoadInspector放在"Assets/Meteor/Editor/"目录下
    新建一个MapLoader类放在"Assets"目录下,此类用于加载地图的静态部分
    新建一个Loader类放在"Assets"目录下,此类用于加载地图的动态部分
    在MapLoader类里增加一个接口,我希望加载指定的地图的时候,就读取指定的地图到我的此场景内,并且把所有地图元素添加到一个地图根节点上
    于是在MapLoader类里设计 public void LoadMap(int level)接口
    在Loader类里设计 public void LoadSceneObjs(int level)接口

    在LoadInspector类里添加

    [CustomEditor(typeof(Loader))]
    public class LoadInspector : Editor
    {
    public override void OnInspectorGUI()
    {
    base.OnInspectorGUI();
    Loader myTarget = (Loader)target;
    if (GUILayout.Button(“AddSceneObj”))
    {
    myTarget.LoadSceneObjs(myTarget.LevelId);
    }
    }
    }
    [CustomEditor(typeof(MapLoader))]
    public class MapLoaderInspector : Editor
    {
    public override void OnInspectorGUI()
    {
    base.OnInspectorGUI();
    MapLoader myTarget = (MapLoader)target;
    if (GUILayout.Button(“Load”))
    {
    myTarget.LoadMap(myTarget.levelId);
    }
    }
    }
    新建一个场景叫MapLoader,创建一个空物体(Map),放在原点(0,0,0)各项属性都重置,无位置,旋转,缩放为1
    在这个物体上加上我们创建的2个组件类,一个MapLoader,一个Loader
    在解读一个地图前,我们需要定义一些资源解析类
    DesLoader,GmbLoader,GmcLoader
    由于代码量还是有一些,所以还是放图和工程吧

    由于原本的游戏工程关联性比较强,所以一些还未实现的模块类似脚本模块,暂时注释起来了,这个工程只加载了地图和场景道具
    而且内置的表格数据,关卡id 15对应的炎硫岛地图,所以里面的id值都填的15 地图表格文件为fubenbase.txt,第一个就是id goodlist就是关卡的物品列表sn14之类的

    还是说几点。
    第一。由于任意一个关卡,可能引用到任意模型,等数据,所以整个模型,以及贴图等资源都得放到工程里,所以会导致工程比较大【此工程只导入了sn14的地图贴图,要导入其他的地图,需要自行导入贴图以及des gmb文件】

    第二。由于配置里写明了有些模式下,一些物品是不应该出现的,但是工程里是全部显示,所以可能与游戏里剧情模式下物品不是一致的

    第三。流星里的shader和灯光是全部忽略了的,所以地图和实际显示不一致,这个要自己按照原本流星的shader来调整,这部分由于不是太熟悉动态控制创建shader并且设置二层的叠加模式,以及一些参数,所以都忽略了
    (我个人知道的都是事先写好一个模式的shader来使用,如果是这样,就需要把流星使用的shader列一个表,按照每个类型的shader一个个来写。不过这样比较耗时,每种shader组合的方式(参数,包括有无uv动画,是否双面,是否透明,以及叠加模式等等)也是太多)

    好了,附上一张图

    工程地址
    http://pan.baidu.com/s/1gfh8tph

    代码里有个BUG,那就是不但实体名称要相同而且实体的序号也要和gmb里的模型序号相同,否则2个同名称的实体,会导致模型错乱,在皇天城,有二座屋子的楼顶会因此无法正常显示[只需要序号相同,客户端不会判定名字]

    2017/10/12更新
    地图读取逻辑应该是
    GMB里每个对象有顶点组(右手坐标系)
    DES文件里有该对象的世界变换矩阵(右手坐标系)
    读取时,先用顶点按照 世界变换矩阵的逆矩阵,变换到自身坐标系
    然后创建一个GameObject。添加MeshFilter时,把变换到自身坐标系的数据写进其Mesh中
    然后设置该GameObject的世界变换矩阵为 Des中该对象的一样(坐标系要转换)
    这样再针对材质和Mesh用AssetDataBase.CreateAsset(mat[x], “xx.mat”) CreateAsset(mesh[x], “xx.asset”)存储到本地目录中
    这样任意一个地图加载后,如果要其中的某个子物件就可以直接找到mesh使用了,很容易就可以修改地图

    保存材质列表,需要说一下,一般流星的地图,会这样设计材质引用
    首先是材质列表 简单来说,就是一个unlit/texture加他指定的一张贴图
    其次是shader列表
    这样我们先生成 与材质列表数量一样多的材质,Material[] mat = new Material[gmb.TexturesCount];
    然后对每个材质,赋上贴图
    然后针对每个Mesh的每个三角面,都会引用一个shader,而这个shader里又会指明用哪个序号的材质,这样每个Mesh的每个子网格都有一个材质球和一个shader共同起作用。
    材质球记录了贴图等一些shader相关属性的数据,shader决定blend算法,以及是否双面 透明度

    假设以金华城的一个屋子 ian1633作为示例
    读取sn13.gmb
    会得到其中有
    45张贴图

    我们创建45个材质

    	   string shaders = "Unlit/Texture";
        //shaders = "Mobile/Diffuse";
        Material[] mat = new Material[gmb.TexturesCount];
        for (int x = 0; x < gmb.TexturesCount; x++)
        {
            mat[x] = new Material(Shader.Find(shaders));
            string tex = gmb.TexturesNames[x];
            int del = tex.LastIndexOf('.');
            if (del != -1)
                tex = tex.Substring(0, del);
            Texture texture = Resources.Load<Texture>(tex);
            //这里的贴图可能是一个ifl文件,这种先不考虑,手动修改
            if (texture == null)
                Debug.LogError("texture miss:" + tex);
            mat[x].SetTexture("_MainTex", texture);
            mat[x].name = string.Format("{0:D2}_{1:D2}", level, x);
            AssetDatabase.CreateAsset(mat[x], "Assets/Materials/" +  level + "/" + mat[x].name + ".mat");
            AssetDatabase.Refresh();
        }
    

    48个shader

    然后这个mesh引用了22,16,15,17,19,这5个shader
    22号引用的第20号贴图 贴图名称sn13h07.jpg
    16号引用的第15号贴图,贴图名称sn13h03.jpg

    这样通过引用贴图,实际就是找到了对应的材质球,然后赋给网格材质球数组就可以了
    List targetMat = new List();
    targetMat.Add(mat[shader数据里的贴图序号]);
    MeshRenderer mr = objMesh.AddComponent();
    mr.materials = targetMat.ToArray();
    这样网格就可以勉强显示出贴图了(shader里其他的参数对效果起了很大影响,但是在这里统一使用的是unlit/texture无光照贴图,这样地图上很多东西显示还是不正常的)

    举个例子,在地图里有一些阴影贴图,代表模型受光照后投射到地面(或其他模型)上的影子。
    还是用金华城举例子
    他第二个元件就是
    sd003,命名上就有shadow003缩写的示意,他是一个桥下的阴影
    所以我们在之前的基础上给这个模型的材质,换一个shader用来显示阴影
    shader分有光和无光跟光照有关系。
    总之现在的手游采用的方式都是 场景静态物烘产生Lightmap,动态物用light probe打区域灯,角色用projector做投影。
    【流星的是无光照的,是用模型顶点颜色乘到贴图颜色上的,就是shader里,带顶点颜色,最后相乘】
    完毕后让sd003的材质球使用这个shader,然后在材质球参数上调整Times参数,就可以看到这个阴影贴图,会变暗或者变亮,看上去就好像阳光照在其上的物体所形成的阴影一样

    Times参数很大,导致很暗

    调整shader的参数Times后像阴影了
    这个跟贴图的透明通道有关系,有时候从灰度生成透明通道,还可以选择透明通道的显示为透明
    读取地图的时候,应该自己复制个unlit/texture的shader,在frag函数用uv和贴图得到颜色后,用结果乘上顶点色,那么整个场景氛围就和原作一样了.

    validate