流星里地图文件读取出来需要解析 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 做投影。
完毕后让 sd003 的材质球使用这个 shader,然后在材质球参数上调整 Times 参数,就可以看到这个阴影贴图,会变暗或者变亮,看上去就好像阳光照在其上的物体所形成的阴影一样
Times 参数很大,导致很暗
调整 shader 的参数 Times 后像阴影了
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于