Unity加载火炬之光的场景

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

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

火炬之光的场景涉及到几个部分: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,os

BLENDER = 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);

convert_mesh.py

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_fbx

def 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]);

convert_mesh_259.py

##########################################################
# 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_key

sys.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_fbx

OGRE_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加载火炬之光的场景

时间: 2024-09-30 10:15:27

Unity加载火炬之光的场景的相关文章

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

转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉加载,网上的Demo太多太多了,这里不是介绍怎么去实现下拉刷新上拉加载,而是针对下拉刷新上拉加载常用的一些应用场景就行了一些总结,包含了下拉刷新上拉加载过程中遇到的一些手势冲突问题的解决方法(只能算是抛砖引玉). 去年9月的时候,那时自己正在独立做Android项目.记得刚刚写完那个ListView列表页面(木有下拉刷新,上拉加载)

Unity加载二进制数据

[Unity加载二进制数据] The first step is to save your binary data file with the ".bytes" extension. unity will treat this file as a TextAsset. As a TextAsset the file can be included when you build your AssetBundle. Once you have downloaded the AssetBun

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

??各位朋友,大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是http://qinyuanpei.com.今天想和大家交流的是解析obj模型并将其加载到Unity3D场景中,虽然我们知道Unity3D是可以直接导入OBJ模型的,可是有时候我们并不能保证我们目标客户知道如何使用Unity3D的这套制作流程,可能对方最终提供给我们的就是一个模型文件而已,所以这个在这里做这个尝试想想还是蛮有趣的呢,既然如此,我们就选择在所有3D模型格式中最为简单的OBJ模型来一起探讨这个问题吧! 关于OBJ模

Unity 侦听进入播放模式、Unity加载时初始化编辑器类

"Editor"文件夹下的PlayModeStateChangedHandler.cs using UnityEngine; using UnityEditor; //允许在 Unity 加载时初始化编辑器类,无需用户操作. [InitializeOnLoadAttribute] public static class PlayModeStateChangedHandler{ //初始化类时,注册事件处理函数 static PlayModeStateChangedHandler(){

Unity加载模块深度解析(Shader)

作者:张鑫链接:https://zhuanlan.zhihu.com/p/21949663来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 接上一篇 加载模块深度解析(二),我们重点讨论了网格资源的加载性能.今天,我们再来为你揭开Shader资源的加载效率. 这是侑虎科技第59篇原创文章,欢迎转发分享,未经作者授权请勿转载.同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨.(QQ群465082844) 资源加载性能测试代码 与上篇所提出的测试代码一样,我们

全面理解Unity加载和内存管理

Unity里有两种动态加载机制:一是Resources.Load,一是通过AssetBundle,其实两者本质上我理解没有什么区别.Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时动态加载,可以指定路径和来源的. 其实场景里所有静态的对象也有这么一个加载过程,只是Unity后台替你自动完成了. 详细说一下细节概念:AssetBundle运行时加载:来自文件就用CreateFromFile(注意这种方法

加载cocos studio场景

今天尝试加载cocos studio的场景. 新版的cocos studio中,"导出"选项变成了"发布".发布之后会生成一个res文件夹,其中每个场景有一个.csb文件,在c++代码中,可以调用CSLoader::createNode直接加载这个csb文件,再把加载后生成的Node放入Scene中. 来自为知笔记(Wiz)

Unity加载模块深度解析(网格篇)

在上一篇 加载模块深度解析(一)中,我们重点讨论了纹理资源的加载性能.这次,我们再来为你揭开其他主流资源的加载效率. 这是侑虎科技第53篇原创文章,欢迎转发分享,未经作者授权请勿转载.同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨.(QQ群465082844) 资源加载性能测试代码 与上篇所提出的测试代码一样,我们对于其他资源的加载性能分析同样使用该测试代码.我们将每种资源均制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到不同硬件设备上的资

用TWaver加载大型游戏场景一例

游戏中经常会出现一些大型的户外场景,例如一个小镇.一座古城等.通常这种场景中包含了较多的建筑.道路.桥梁等等元素,其3D模型比较大且复杂.在使用TWaver加载时,可使用一些技巧,让加载速度更快.显示更流畅. TWaver 3D支持导入json或obj等格式的3D场景.如果是obj格式,可将obj文件.mtl文件.所有贴图文件,以字符串参数形式传给TWaver进行加载.当然这些文件也都可以是网络上的URL字符串. ? 1 2 var loader = new mono.OBJMTLLoader(