原文  http://blog.csdn.net/langresser_king/article/details/38423793

因为一些基础的数学问题,前前后后一共研究了四五天,今天终于有些眉目了,记录下来备忘。

一、火炬之光场景配置文件分析

火炬之光的场景涉及到几个部分:1、资源文件,包含基础的模型、粒子、怪物等等。我们暂时只看模型,就是一个一个的mesh文件,同时几乎每个模型都有对应的缩略图文件(xxxthumb.jpg)和碰撞体文件(xxxcollision.mesh)。    2、Tileset配置,这个是一个dat文件,例如catacomb.dat,里面包含了几千个PIECE,而piece则对应实际的资源文件以及一些编辑器相关的参数配置,比如对齐量。piece与资源可以是一对多的关系,这个是随机场景生成用到的。     3、layout,这个在layouts文件夹下面,就是实际场景的配置。可以使用Guts编辑器打开layout文件来查看场景。配置文件里面[BASEOBJECT]就是实际场景中的一个元素,最常见的就是通过GUID指定一个RoomPiece也就是Tileset中的一个元素。

解析这个配置文件没有什么难度,顺着每行遍历下去一行行解析就可以获取到每个元素的内容了。

二、Unity自动加载场景

我们先不考虑动态场景生成,直接选择一个layout配置文件,然后对应的创建物件。核心代码如下:

[MenuItem("Tools/导入场景(测试)")]
  static void LoadTestScene() {
    Dictionary<string, LevelTileSet> allPieces = LoadAllPiece();

    LevelLayout layout = new LevelLayout();
    layout.ParseFile("Assets/Model/Map/layouts/testroom/1x1single_room_a/testroom.layout");
    BuildLayout(allPieces, layout.allTiles);
  }

  static void BuildLayout(Dictionary<string, LevelTileSet> allPieces, List<LevelLayoutTile> allTiles) {
    foreach (var item in allTiles) {
      foreach (var tileset in allPieces) {
        if (item.pieceGuid == null || item.pieceGuid.Length == 0) {
          continue;
        }        LevelTilePiece piece = tileset.Value.SearchPiece(item.pieceGuid);
        if (piece != null && piece.filePath.Count > 0) {
          string assetPath = GetRealFilePath(piece.filePath[0]);
          Debug.Log(piece.filePath[0] + "  " + assetPath);
          //break;
          GameObject obj = AssetDatabase.LoadAssetAtPath(assetPath, typeof(GameObject)) as GameObject;
          Debug.Log(obj);
          GameObject obj2 = Instantiate(obj) as GameObject;
          obj2.transform.position = new Vector3(item.position.x, item.position.y, item.position.z * -1);
          float angleX = -Mathf.Asin(item.rotationForward.y) / Mathf.PI * 180;
          float angleY = Mathf.Atan2(item.rotationForward.x, -item.rotationForward.z) / Mathf.PI * 180;
          float angleZ = Mathf.Atan2(item.rotationRight.y, item.rotationUp.y) / Mathf.PI * 180;
          Debug.Log(string.Format ("rforward:{0}   rright:{1}   rup:{2}     ax:{3} ay:{4}   az:{5}",
              item.rotationForward, item.rotationRight, item.rotationUp, angleX, angleY, angleZ));

          obj2.transform.rotation = Quaternion.Euler(new Vector3(angleX, angleY, angleZ));
          obj2.transform.localScale = new Vector3(item.scale.x, item.scale.y, item.scale.z);
        }
      }
    }
  }

这里直接实例化一个fbx模型,在实际应用中我们应该先创建好prefab,然后实例化prefab,这样无论是在优化的角度,还是工程的角度都是有帮助的。

三、加载火炬之光的资源:

关于如何从火炬之光导出资源我之前写过一篇文章,但是年代久远,后面针对实际问题做了几次更新。比如写个脚本进行批量的转换;预先判断xml文件的存在,并且不是转换完一个文件就删除,这个可以大大提高批量转换的速度;无动画的文件可以使用最新的blender来导出,这个倒没有明显的好处,不过由于不熟悉blender的api,而blender的api在2.5版本做了大量面目全非的修改,所以两个版本的导出脚本或功能脚本无法相互转换。下面是我现在使用的脚本:

auto_covert_mesh.py

import glob,sys,osBLENDER = r"E:\MyProj\blender-2.49b-windows\blender";
DUMMY_BLEND = r"E:\MyProj\unity3d\arpg36\ARPG\Tool\dummy.blend"
CONVERT_SCRIPT = r"E:\MyProj\unity3d\arpg36\ARPG\Tool\convert_mesh.py"BLENDER259 = r"E:\MyProj\blender-2.71-windows64\blender";
DUMMY_BLEND259 = r"E:\MyProj\unity3d\arpg36\ARPG\Tool\dummy.blend"
CONVERT_SCRIPT259 = r"E:\MyProj\unity3d\arpg36\ARPG\Tool\convert_mesh_259.py"def convert_path(path, animation):
  for root, dirs, files in os.walk(path):
    for dir in dirs:
      strDir = os.path.join(root, dir);
      #print(strDir);    for file in files:
      file = file.lower();
      strFile = os.path.join(root, file);
      #print(strFile);
      if strFile.find(".mesh") != -1 and strFile.find(".meta") == -1 and strFile.find(".xml") == -1:
        output = strFile.replace(".mesh", ".fbx");
        if not os.path.exists(output):
          print("--------------" + strFile);
          if animation:
            os.system("{0} -b {1} -P {2} -- {3}".format(BLENDER, DUMMY_BLEND, CONVERT_SCRIPT, strFile));
          else:
            os.system("{0} -b {1} -P {2} -- {3}".format(BLENDER259, DUMMY_BLEND259, CONVERT_SCRIPT259, strFile));

  #return
  for root, dirs, files in os.walk(path):
    for file in files:
      file = file.lower();
      if file.find(".mesh") != -1 or file.find(".skeleton") != -1 or file.find(".xml") != -1 or file.find(".material") != -1 or file.find(".adm") != -1:
        strFile = os.path.join(root, file);
        os.remove(strFile);convert_path(r"E:\Backup\MEDIA_png\levelsets\props\test", False);
import Blender
import bpy
import sys
import os,glob
sys.path.append(r"E:\MyProj\blender-2.49b-windows\.blender\scripts\torchlight");
sys.path.append(r"E:\MyProj\blender-2.49b-windows\.blender\scripts");import importTL,export_fbxdef ImportMesh(file):
  print file;
  scn = bpy.data.scenes.active
  #Scene.Unlink(scn);
  importTL.ImportOgre(file);  file = file.lower();
  output = file.replace(".mesh", ".fbx");  export_fbx.fbx_default_setting();
  export_fbx.fbx_write(output);
  return True;ImportMesh(sys.argv[6]);
##########################################################
# Custom Blender -> Unity Pipeline
# http://www.mimimi-productions.com, 2014
# Version: 1.9
# Only for Blender 2.58 and newer
#
# Thanks to kastoria, jonim8or and Freezy for their support!
# Special thanks to Sebastian hagish Dorda for implementing the sort methods.
# http://www.blenderartists.org
##########################################################
# Fixes the -90 degree (x-axis) problem for Unity.
# Artists and coders simply work as they should.
# -> No more custom rotation-fixes in Unity or Blender.
##########################################################
# HISTORY
# 1.9, CLEANUP -- removed support for old Blender versions, only support 2.58 and newer
# 1.8, FIX -- applies transforms in order (parents prior childs)
# 1.7, FIX -- shows hidden objects prior importing
# 1.6b, FIX -- Apply mirror modifiers before rotating anything else
# 1.6a, FIX -- deselect all objects, otherwise deleting wrong objects when using UNITY_EXPORT flag
# 1.6, FEATURE -- support UNITY_EXPORT flag --> set via MiBlender-Tools or e.g.: bpy.data.objects['enemy_small']['UNITY_EXPORT'] = False
# 1.6, FIX -- now import empties
# 1.5, FIX -- make all objects single user, otherwise instances can't be imported
# 1.4, FIX -- show all layers, necessary for rotation fix
# 1.3, FIX -- location constraints are now deactivated (due to rotation prior to export)
# 1.2, FIX -- apply rotation worked only on selection! (thx jonim8or)
# 1.1, FIX -- object mode doesn't need to be set in file anymore
##########################################################
# TO DO
# ISSUE -- do not use empties as parents (rotation can't be applied to them!)
# ISSUE -- transform animations are missing because we are not exporting the default take --> thus only bone-animations are working?!
# ISSUE -- LIMIT_LOCATION animation constraint is forbidden! Will be muted and not work in engine (anim might look different compared to Blender)
# 2.0, FEATURE -- support UNITY_EXPORT_DEFAULT_TAKE --> can export no-bone-animations to Unity
##########################################################import bpy
import sys
import os,glob
import os
import time
import math  # math.pi
from mathutils import Vector, Matrix
from functools import cmp_to_keysys.path.append(r"E:\MyProj\blender-2.71-windows64\2.71\scripts\addons\torchlight");
sys.path.append(r"E:\MyProj\blender-2.71-windows64\2.71\scripts\addons\io_scene_fbx");import TLImport,export_fbxOGRE_XML_CONVERTER = r"E:\MyProj\unity3d\arpg36\OgreCommandLineTools_1.7.2\OgreXmlConverter.exe"def ImportMesh(file):
  print(file);
  TLImport.load(None, bpy.context, file, OGRE_XML_CONVERTER, False);  # SORTING HELPERS (sort list of objects, parents prior to children)
  # root object -> 0, first child -> 1, ...
  def myDepth(o):
    if o == None:
      return 0
    if o.parent == None:
      return 0
    else:
      return 1 + myDepth(o.parent)  # compare: parent prior child
  def myDepthCompare(a,b):
    da = myDepth(a)
    db = myDepth(b)
    if da < db:
      return -1
    elif da > db:
      return 1
    else:
      return 0  # Operator HELPER
  class FakeOp:
    def report(self, tp, msg):
      print("%s: %s" % (tp, msg))  # Rotation matrix of -90 around the X-axis
  matPatch = Matrix.Rotation(-math.pi / 2.0, 4, 'X')  # deselect everything to close edit / pose mode etc.
  bpy.context.scene.objects.active = None  # activate all 20 layers
  for i in range(0, 20):
    bpy.data.scenes[0].layers[i] = True;  # show all root objects
  for obj in bpy.data.objects:
    obj.hide = False;  # make single user (otherwise import fails on instances!) --> no instance anymore
  bpy.ops.object.make_single_user(type='ALL', object=True, obdata=True)  # prepare rotation-sensitive data
  # a) deactivate animation constraints
  # b) apply mirror modifiers
  for obj in bpy.data.objects:
    # only posed objects
    if obj.pose is not None:
      # check constraints for all bones
      for pBone in obj.pose.bones:
        for constraint in pBone.constraints:
          # for now only deactivate limit_location
          if constraint.type == 'LIMIT_LOCATION':
            constraint.mute = True
    # need to activate current object to apply modifiers
    bpy.context.scene.objects.active = obj
    for modifier in obj.modifiers:
      # if you want to delete only UV_project modifiers
      if modifier.type == 'MIRROR':
        bpy.ops.object.modifier_apply(apply_as='DATA', modifier=modifier.name)  # deselect again, deterministic behaviour!
  bpy.context.scene.objects.active = None  # Iterate the objects in the file, only root level and rotate them
  for obj in bpy.data.objects:
    if obj.parent != None:
      continue
    obj.matrix_world = matPatch * obj.matrix_world  # deselect everything to make behaviour deterministic -- instead of "export selected" we use the UNITY_EXPORT flag
  for obj in bpy.data.objects:
    obj.select = False;  # apply all(!) transforms
  # parent prior child
  for obj in sorted(bpy.data.objects, key=cmp_to_key(myDepthCompare)):
    obj.select = True;
    # delete objects with UNITY_EXPORT flag
    # if flag not saved, then assume True
    if obj.get('UNITY_EXPORT', True) == False:
      bpy.ops.object.delete()
    # apply transform if not deleted
    else:
      bpy.ops.object.transform_apply(rotation=True)
    # deselect again
    obj.select = False;  file = file.lower();
  output = file.replace(".mesh", ".fbx");
  export_fbx.save(None, bpy.context, filepath=output, global_matrix=None, use_selection=False, object_types={'ARMATURE', 'EMPTY', 'MESH'}, use_mesh_modifiers=True, use_armature_deform_only=True, use_anim=True, use_anim_optimize=False,use_anim_action_all=True, batch_mode='OFF', use_default_take=False);
  return True;ImportMesh(sys.argv[6])

四、遇到的一些困难和问题

1、Z轴向上还是Y轴向上。 Blender和3DMax都是Z轴向上的,而Unity3D则是Y轴向上。 其他的一些软件比如Maya默认是Y轴但是可以自行设定;Unreal貌似是Z轴向上。 具体哪个轴向上并没有绝对的好或者不好,不过不同动画制作软件和游戏引擎之间的不兼容确实会让人感到恶心。   大多数情况下Blender或者3DMax的导出插件会帮我们做好这个调整,来保证软件和引擎中的视觉效果是一致的。但是Blender做的还不够好,它的fbx导出里面有设置全局的旋转矩阵,在2.4版本的导出插件里面,我们可以直接设置RotX-90,在最新的2.7版本里面则是选择Forward方向和Y方向对应哪个轴,无论哪个在代码中的本质都是设置一个Global Matrix。 不过这样做的问题是导出的物体在Unity里面显示的是x轴旋转-90,虽然视觉效果是一致的,但是由于这个旋转轴的设置导致我们实际代码操作时会有一些不爽。如果你同时导出Camera和Lamp,则会有一个父节点,进行旋转的是实际的模型,而父节点则不进行旋转,正因如此早些时候并没有发现这个问题,因为一切看起来都是正确的。  在上面的convert_mesh_259里面有一大坨代码用来修正这个问题,也就是我们直接在Blender里面旋转模型,然后不设置旋转矩阵,这样Blender和Unity就完全对的上了。 这应该算是一个比较完善的解决方案。

2、旋转矩阵还是欧拉角。   火炬之光的配置文件使用了三个方向的向量来表示转向(Orientation),分别是Forward(对应z轴) Right(对应x轴)和Up(对应y轴)。这三个方向的向量共同组成了一个旋转矩阵(这个矩阵是根据绕每个轴的欧拉角旋转矩阵按照固定顺序相乘得来,矩阵应该按照x y z的顺序来排,然后按照z x y的顺序相乘),假定按z轴旋转z角度,按x轴旋转x角度,按y轴旋转y角度,则矩阵公式为(其中Cz代表cos(z)   Sz代表sin(z)):

Cz*Cy + Sx*Sy*Sz       Sz*Cx          -Sy*Cz+Sz*Sx*Cy--------------Right--->X

-Sz*Cy+Cz*Sx*Sy        Cz*Cx          Sy*Sz+Sx*Cy*Cz--------------Up------->Y

Cx*Sy                          -Sx               Cx*Cy---------------------------Forward->Z

对应到火炬之光中的配置则是:

Right.x       Right.y       Right.z

Up.x            Up.y          Up.z

Forward.x    Forward.y   Forward.z

它们的默认值是:

1   0     0

0   1    0

0   0    1

我们已经知道了Right.x/y/z  Up.x/y/z    Forward.x/y/z,这些也就是火炬之光的编辑器中对应的三个向量,求解上面的矩阵可以得到:

x = -sin(Forward.y)

y = atan2(Forward.x,  Forward.z)

z = atan2(Right.y, Up.y)

这样我们就可以根据火炬之光的配置得到Unity需要的欧拉角(transform.rotation)。如果配置中某一个轴没有配置,则取默认值。

3、左手坐标系还是右手坐标系。

火炬之光是右手坐标系,默认z轴正方向向外。 Unity是左手坐标系,默认z轴正方向向里。 现在涉及到一个右手坐标系到左手坐标系的转换。这里分平移矩阵的转换和旋转矩阵的转换。平移矩阵很好办,直接把z坐标取反就可以了。旋转矩阵搜索了半天也没有一个明了的答案,这里我直接把Forward.z取反,然后代到上面的公式中求得一个新的z,貌似结果是OK的。

结合2、3两点,就是最上面第二大项中的具体代码实现。

五、后续问题的整理

1、灯光。实现了模型的加载只是最基础的一步。如果要达到火炬之光的效果还需要很多细节的调整,比如shader看情况要重写,而不是使用默认的Diffuse。灯光也是很重要的一部分,火炬之光2和暗黑3的整体效果就是靠灯光来烘托出来的,这里说的效果并不是实时阴影、光线追踪之类的高级渲染技术,而是最基础的灯光的明暗、色调、范围的设置。如果这些设置好了,就可以获得非常舒服的体验。 在手机上面主要使用LightingMap来对场景进行烘焙,这样可以达到理想的效果和效率。

2、粒子效果。 一个游戏炫不炫主要就看光效,这里不讨论次时代大作,而是在设备受限或者资金受限或者人员能力受限的情况下,什么是最能抓住人眼球的东西。 光效做的好了,一个简单的动作都可以设计出非常酷的技能,一个简单的武器模型配上一个发光的粒子光效就可以变成一把极品传承装备,即便人物做的差一些,配上个环绕的雷电光效也可以冒充雷神。  粒子效果最主要的应用场景有这么几个:技能、武器的发光效果、场景中的火焰等环境效果、喷血尸体爆炸等特殊表现。 火炬之光把两点做到极致就获得了非常棒的打击感,一个是受击的光效、屏幕震动、音效的配合,一个是怪物死亡后的炸飞、炸裂等表现。

不过说了这么多,粒子效果却无法复用,悲剧。 如果技术够强,可以考虑整体移植粒子系统,类似的工作Xffector已经做了。 但是考虑到技术难度和工作量,这个只是理论上可行。

3、动态生成场景。  原本我以为火炬之光的场景动态生成是非常高级的技术,不过后来研究了下,它只是使用非常简单的设置就达到了动态场景生成的目的。 首先每个[PIECE]中都可以包含多个模型,这些模型在生成的时候会随机进行选择,这样场景中的细节每次看都会不一样。然后针对同一个关卡区域设计出很多不同的场景,场景的样式可以千变万化,只要满足最基础的任务设置、场景链接的设置、出口入口的设置符合一定规则就可以了。 在生成整个关卡的时候会根据layout chunk的规则随机选择合适的区域块(就是我们上面实现的加载的一个layout)共同组成一个大的场景。

Unity加载火炬之光的场景相关推荐

  1. 火炬之光模型导出(Unity加载火炬之光的模型)

    先说明几点,导出方案可行,测试通过. python和blender的版本一定要用下文中所说的.新的Python或者是新的Blender版本都无法完美导入. 导入导出脚本可以选择 (http://cod ...

  2. Unity尝试制作王者荣耀(十六)——FightScene(一)加载模型到场景中

    一.进入战斗场景动态加载相关模型 1.1. 首先新建一个脚本FightScene.cs告诉服务器,客户端已经进入Fight场景 void Start () {//告诉服务器进入到了战斗场景this.W ...

  3. sceneManager.loadscene加载场景时不会主动去加载场景的依赖包,要手动加载或添加场景到build setting列表中...

    sceneManager.loadscene加载场景时不会主动去加载场景的依赖包,要手动加载或添加场景到build setting列表中 假设有一场景1001.unity,,manifest文件如下: ...

  4. Unity加载进度条

    转载自:http://www.58player.com/blog-2537-89690.html 背景           通常游戏的主场景包含的资源较多,这会导致加载场景的时间较长.为了避免这个问题 ...

  5. unity 加载关卡_unity中加载新关卡函数简单用法

    Application.LoadLevel 加载关卡 static function LoadLevel (index : int) : void Description描述 Loads the le ...

  6. Unity加载倾斜摄影模型/激光点云,开源

    [重大更新]现已支持WebGL 业余时间尝试了下用Unity加载倾斜摄影模型/激光点云,目前支持Bentley ContextCapture生成的3MX格式 源码见 https://github.co ...

  7. 解析OBJ模型并将其加载到Unity3D场景中

    版权声明:本文由秦元培创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处,本文作者为秦元培,本文标题为解析OBJ模型并将其加载到Unit ...

  8. Android 下拉刷新上拉加载 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉加载,网上的Demo太多太多了,这 ...

  9. unity 加载完场景继续加载场景中的物体_Unity光照渲染原理

    先来介绍几个重要概念 直接光照(Direct Lighting):光源直接照射到物体表面所以产生的光照信息,进入Unity主场景加载的就是直接光照 间接光照(Indirect Lighting):光源 ...

最新文章

  1. native react 常用指令_React-Native 常用命令
  2. shell shock 执行漏洞分析
  3. 如何养成一个习惯(持续更新)
  4. 学校通用计算机保护,浅谈学校计算机教室软件系统的保护.docx
  5. python Celery 分布式任务队列快速入门
  6. @PropertySource与@ConfigurationProperties多种方式读取配置文件详解,附带@PropertySources使用说明
  7. mysql 学习笔记08 日期相关函数2
  8. 求矩阵中各列数字的和
  9. 新版ubuntu中打开终端的方法和安装ssh 的方法
  10. WPF ListView中自动生成的列
  11. 订阅号获取openid_小程序订阅消息
  12. Ynoi2019模拟赛划水记
  13. 谷歌电子市场第4天总结
  14. html 英文艺术字体,生日快乐英文艺术字体
  15. 总有一种正能量触动你的心灵,读刘丁宁的一封信
  16. python日期计算,Python 日期的转换及计算的具体使用详解
  17. dw选项卡代码_Dreamweaver 中的 Spry 选项卡 Tabbed Panels 自定义外观的一些细节
  18. 出门问问AIGC SaaS平台亮相数贸会 赋能内容创作全流程
  19. 7-3 查找奥运五环色的位置 (25分)
  20. [HCIP-IoT Developer V2.5 题库] 201-216 题 华为

热门文章

  1. 常见多变量/多元统计分析方法分类图
  2. php无限极分类整理
  3. vim复制粘贴的命令
  4. 上行OFDMA接入机制(UL-OFDMA)
  5. IBM沃森会成为第一个被抛弃的AI技术吗?
  6. Firecracker
  7. RGB565 RGB888
  8. 人工神经网络的典型模型,人工神经网络模型定义
  9. 小程序自定义搜索框_将自定义搜索提供程序添加到Windows 7以及高级搜索技巧
  10. 文本相似度的几种计算方式