unreal 加载 手动 模型 - CSDN
  • 47. 模型加载

    2019-01-08 09:08:37
    在现代游戏中,人物和模型由针对每个对象的成千上万个多边形组成。对纹理映射而言这非常复杂,因为它需要为每个多边形的每个顶点设置一对TU和TV纹理坐标。假定有一个包含了5000个三角形的模型。假定不涉及加索引的...

    在现代游戏中,人物和模型由针对每个对象的成千上万个多边形组成。对纹理映射而言这非常复杂,因为它需要为每个多边形的每个顶点设置一对TU和TV纹理坐标。假定有一个包含了5000个三角形的模型。假定不涉及加索引的几何图形(索引),而只使用三角形,那么在整个模型中将有15000个顶点。至此,除了内置的Direct3D以外,还得手动指定几何图形数据。如果一个模型包含5000个多边形,那么试着指定这些多边形中的每个几何图形数据,就会是一场噩梦。同时还要为顶点手动设置纹理坐标,从时间角度而言,这也是不可以接受的,并且绝对不现实。即使设法对所有模型都做了这一点,那么也会存在将这些信息硬编码到源代码中,以及其他游戏不可使用相同资源的问题。所要做的工作就是可以比较简单地开发资源,并能够从游戏程序外获取这些资源。幸运的是,本章将介绍这些内容。

      模型加载介绍
           
    本章旨在介绍将不同类型文件中的几何图形数据加载到Direct3D程序中的方法。关于这方面,最好的内容就是可以在场景中包含复杂的和施加了纹理的对象,而这只需添加几行加载和显示数据的代码。然后可以在多个程序中使用这些对象,将几何图形信息从一个游戏中复制粘贴到另一个游戏中。

    加载模型的方法有很多。现实中有许多不同类型的模型文件格式。文件格式只是在特定文件中保存信息的方式。某些文件格式保存顶点、法线和纹理坐标。某些文件格式除了保存前面所有的内容,还包括三角形索引和材质信息。使用或创建的文件格式将取决于对游戏的要求。本章将介绍Direct3D中三种不同的模型文件格式。这三种模型分别是Direct(X)模型、OBJ模型和最终模型格式(Ultimate Model Format,UMF)。最终所要做的全部工作就是在从文件加载模型时了解信息在文件中的存储方式。如果文件格式先是保存了所有的顶点,然后是所有的纹理坐标,那么同样要以这样的顺序加载它。换句话说,为了加载顶点和纹理坐标,就必须了解它们在文件中的位置。有许多可用于开发模型的软件。这些软件包括3D Studio Max、LightWave、Maya、TrueSpace和MilkShape3D。

    模型文件
           下面将要介绍的第一种模型文件格式是X模型文件。该文件是以X为扩展名的DirectX模型,它们既可以是文本文件,也可以是二进制文件。本书介绍的第二种模型是OBJ模型。通常可以从诸如3D Studio Max这样的建模软件导出这些常用的文本文件。这意味着可以在操作系统附带的记事本程序中打开OBJ模型,并查看和编辑模型数据。本书接下来介绍的最后一类模型是在www.UltimateGameProgramming.com上开发的名为UMF的通用模型文件格式。该类文件中的每一项都和其他的完全不同。这样可以在加载不同的可用模型格式时积累经验。

        对Direct3D中的X文件格式而言,这里将使用内置的Direct3D函数加载、渲染和释放X模型。目前,也许读者已猜到,Direct3D提供了大量有用的函数,可以方便地处理从模型渲染到光照到文本以及更多的图形学方面的不同内容。

    不同的建模软件可以加载、导入或导出不同类型的模型文件。一些软件也许支持某些其他软件不支持的文件类型。在寻找适合自己使用的建模软件时一定要牢记这一点。MilkShape3D提供了大量的模型文件格式,包含了大多数主要的游戏模型,如雷神之锤3(Quake3)、彩虹6号(Rainbow6)、魔兽争霸3(Warcraft3)、Unreal、英雄本色(Max Rayne)、半条名(Half Life)、英雄萨姆(Serious Sam)和毁灭战士3(Doom3)。

    GMAX是一款可以下载的免费编辑软件,它是由3D Studio Max开发的。GMAX可用于开发模型或级别,并提供一套指导学习该软件的指南。但要注意的是,GMAX除非是开发了游戏包,否则并不能导出模型。为了开发软件包,就要给Discreet公司支付费用。所以虽然它可以免费下载,但最终还是要花银子的。

    使用X文件
           
    DirectX模型或X模型是在Direct3D中使用的模型,可以通过几个内置函数直接加载和渲染到API。X模型最大的优点就是它们十分易用,可以直接和Direct3D交互使用。动画模拟X模型要做大量的工作,但对静态网格X而言,这是个很好的选择。同样,有许多导出程序可以将许多不同的文件格式导出为X格式,反之亦然。X模型格式很受欢迎,并且在不久的将来会得到广泛的使用。

    X文件格式介绍
           X文件格式基于模板格式。基于模板就可以根据定义在文件中的模板来定义X文件中的内容。这意味着可以对所有想用的内容使用X文件,还意味着并不止是可以存储几何图形数据。由于为某些对象使用X文件而未存储几何图形数据已经超出了本书的讨论范围,因此对此介绍很少,读者甚至没有必要了解这方面的内容。

           Direct3D有一些内置模板,可用于定义X文件中的网格。这些标准模板包括外观、纹理坐标和法线。因为X文件格式基于模板格式,所以Direct3D需要知道一些存储在文件中用于某种目的的信息。

           X模型既可以被保存为文本文件,也可以被保存为二进制文件。这意味着可以在任意的文本编辑器中打开X文本文件,并修改其中的内容。同样可以创建一个新文件,并手动填充其中的信息。对复杂模型而言,诸如前面提到的有5000个多边形的模型,就不想手动这样做,可以使用如3D Studio Max这样的建模软件来保存该模型。下面介绍一下由两个三角形组成的方形X模型。将为该模板施加纹理,并为网格添加简单的材质。方形X模型示例如程序清单12.1所示。

    保存纹理方形网格的X文件示例

    xof 0302txt 0032

    Material UgpMat {   // Material 0

       1.0;1.0;1.0;1.0;;       // Face color

       0.0;                    // Power

       0.0;0.0;0.0;;           // Specular color

       0.0;0.0;0.0;;           // Emissive color

       TextureFilename{"ugp.bmp";}   // Texture file name.

    }

    // The square mesh data.

    Mesh Square {

    4;                      // Number of vertices.

    1.0; 1.0; 0.0;,         // Vertice 1

    -1.0; 1.0; 0.0;,        // Vertice 2

    -1.0;-1.0; 0.0;,        // Vertice 3

    1.0;-1.0; 0.0;          // Vertice 4

    2;                      // Number of triangles

    3;0,1,2;,               // Triangle indices 1

    3;0,2,3;,               // Triangle indices 2

    MeshMaterialList {

    1;                      // Number of materials

    2;                      // Number of faces

    0,                      // Face 0 use material 0

    0,                      // Face 1 use material 0

    {UgpMat} // Reference the material.

    }

    MeshTextureCoords {

    4;                // Number of vertices

    0.0; 0.0;,        // Vertex 1 tex coord.

    0.0; 1.0;,        // Vertex 2 tex coord.

    1.0; 1.0;,        // Vertex 3 tex coord.

    1.0; 0.0;;        // Vertex 4 tex coord.

    }

    } // End of Mesh Square

        

    X模型看上去非常像一组C结构。如果分解整个模型文件,那么在最后可以看到,这里并没有包含大量复杂的信息。该文件由多个模板组成,这些模板包括针对网格的模板、针对材质的模板、针对纹理坐标的模板。这些模板都是Direct3D可以理解的标准模板,所以要做的全部工作就是在模型文件中定义这些模板,并设置这些模板。同样还可以在文件中为X文件格式添加注释。这些注释就像C/C++中的一样,其目的只是为了增加模板的可读性。如程序清单12.1所示,很容易描述文件每一部分的工作。

           每个X文件的开头先是一个标识符。这个标识符告诉程序正在读取X文件,而且是一个有效的X文件,这里还包含了文件版本。

    X文件头示例

    xof 0302txt 0032

    文件头开始先是xof。xof意味着程序正在加载某个版本的X文件。后面是文件的主版本号和次版本号:本例中是3.2。版本后面是txt,这意味着程序正在加载的是一个X模型的文本文件,而不是一个二进制文件。文件头最后一部分表示的是程序加载的文件使用的浮点数位数,在本示例文件是32(0032)。

           这里不需要创建任何模板,因为本书正使用的是Direct3D中的标准模板。程序清单12.1中的X示例文件中已经指定了标准模板Material、Mesh、MeshMaterialList和MeshTextureCoords。这里包含大量模板,可以在DirectX SDK文档中的X File Format Reference(X 文件格式参考)一节可以找到。唯一要用到的其他模板是MeshNormals和MeshVertexColors。

    Material 模板
           X文件中的Material标准模板用于指定可以施加在单个表面(多边形)的材质。针对材质的标准模板定义了环境颜色、发射能量、镜面颜色、反射量、与材质相关的纹理图像文件名。读者可以创建许多不同类型的材质,可以在模板中让不同的外观引用它们。这样可以使用包含完全不同材质和/或纹理的部分模型,而不是相同模型网格的所有其他部分。

    从X示例文件中提取的Material模板

    Material UgpMat {

      // Material 0

       1.0;1.0;1.0;1.0;;       // Diffuse color

       0.0;                    // SpecularPower

       0.0;0.0;0.0;;           // Specular color

       0.0;0.0;0.0;;           // Emissive color

       TextureFilename{"ugp.bmp";}   // Texture file name.

    }

     Mesh 模板

           Mesh模板定义X文件中的完整网格。X模型可以包含多个网格。例如,可以在单个模型文件中使用不同的mesh模板将网格分成头网格、上身网格、下身网格。X文件中的这些网格由多个小模板组成,它们嵌入在一个大模板中。网格模板结构开始先定义顶点总数,然后是顶点的x、y、z坐标。定义完顶点之后,接下来必须定义网格的外观或多边形。开始先是网格中的外观数,后面是每个外观的三角形索引。每个三角形索引行开始是代表外观使用的三角形数目,后面才是索引号。定义完顶点和三角形之后,就可以随意定义其他属性了,如每一外观的材质、纹理坐标等。程序清单12.4给出了X示例文件完整的正方形网格模板。

    // The square mesh data.

    Mesh Square {

    4;                      // Number of vertices.

    1.0; 1.0; 0.0;,         // Vertice 1

    -1.0; 1.0; 0.0;,        // Vertice 2

    -1.0;-1.0; 0.0;,        // Vertice 3

    1.0;-1.0; 0.0;          // Vertice 4

    2;                      // Number of triangles

    3;0,1,2;,               // Triangle indices 1

    3;0,2,3;,               // Triangle indices 2

    MeshMaterialList {

    1;                      // Number of materials

    2;                      // Number of faces

    0,                      // Face 0 use material 0

    0,                      // Face 1 use material 0

    {UgpMat} // Reference the material.

    }

    MeshTextureCoords {

    4;                // Number of vertices

    0.0; 0.0;,        // Vertex 1 tex coord.

    0.0; 1.0;,        // Vertex 2 tex coord.

    1.0; 1.0;,        // Vertex 3 tex coord.

    1.0; 0.0;;        // Vertex 4 tex coord.

    }

    } // End of Mesh Square

        

    MeshMaterialList 模板
           MeshMaterialList模板指明网格中的哪个外观使用哪种材质。在X模型示例文件中只定义了一种材质,所以将该材质施加给所有的外观。材质链表结构开始先定义材质数目,然后是将材质施加到外观总数。这之后,每个外观占一行。每行定义一个值,用该值引用要用的材质。该结构的最后一行是涉及到的所有材质链表。第一个材质索引号为0,第二个为1,依此类推。所以对于X示例文件中的每个外观都指定材质索引号为0,因为这里只有一个材质可用。程序清单12.5给出了X示例文件中的MeshMaterialList模板。

    MeshMaterialList {

    1;                      // Number of materials

    2;                      // Number of faces

    0,                      // Face 0 use material 0

    0,                      // Face 1 use material 0

    {UgpMat} // Reference the material.

    }

       

    MeshTextureCoords 模板
           要介绍的最后一个标准模板是MeshTextureCoords模板。有了该模板就可以在Direct3D中将纹理映射到网格上。该模板很容易理解,非常明了。模板开始先指定网格中的索引数。然后用逗号隔开,简单地列出每个顶点的纹理坐标。程序清单12.6给出了X示例文件中的MeshTextureCoords模板。

    MeshTextureCoords {

    4;                // Number of vertices

    0.0; 0.0;,        // Vertex 1 tex coord.

    0.0; 1.0;,        // Vertex 2 tex coord.

    1.0; 1.0;,        // Vertex 3 tex coord.

    1.0; 0.0;;        // Vertex 4 tex coord.

    }

    加载和渲染X模板
           Direct3D不涉及动画时,加载和渲染X模型很简单。所有的X模型都保存在相同的名为LPD3DXMESH的结构中。第3章中的Direct3D内置对象使用了该结构。为了从文件加载X模型,调用D3DXLoadMeshFromX()函数即可。该函数可将X模型加载到LPD3DXMEH对象中,并在Direct3D中使用该模型。D3DXLoadMeshFromX()函数原型如程序清单12.7所示。
    HRESULT D3DXLoadMeshFromX(
    LPCTSTR pFilename,
    // 要加载的X文件的文件名
    DWORD Options, // 创建网格时所使用的创建标记
    LPDIRECT3DDEVICE9 pD3DDevice, // 与该网格对象相关的设备指针
    LPD3DXBUFFER * ppAdjacency, // 返回一个ID3DXBuffer对象,该对象包含了一个
    // 描述该网格对象的邻接信息的DWORD类型的数组
    LPD3DXBUFFER * ppMaterials, // 返回一个ID3DXBuffer对象,该对象包含了一个
    // 存储该网格的材质数据的D3DXMATERIAL类型的结构数组
    LPD3DXBUFFER * ppEffectInstances, // 返回一个ID3DXBuffer对象,该对象包含了一个
    // D3DXEFFECTINSTANCE结构
    DWORD * pNumMaterials, // 返回网格中的材质数目(即有ppMaterials参数输出的D3DXMATERIAL数
    // 组中元素的个数)
    LPD3DXMESH * ppMesh // 返回所创建的并已填充了X文件几何数据的ID3DXMesh对象.
    );

      D3DXLoadMeshFromX()函数的参数包括X文件的文件名、加载网格的选项标识符、Direct3D设备对象、存储邻近数据(三角形索引)的LPD3DXBUFFER、存储定义在文件中的材质的缓存、存储在文件中使用的效果(阴影器)实例的缓存、指向材质总数的指针以及该函数调用创建的网格对象的地址。本书中,该函数只涉及前三个参数和最后一个参数。其他参数可选。本书并不使用涉及效果实例的参数,因为效果文件,即DirectX高级编程阴影器,涉及到高级图形学的内容,超出了本书的讨论范围。如果该函数返回D3D_OK,则表示加载成功。否则,加载过程出现错误,模型没有被加载到内存中。

           使用内置的Direct3D对象渲染X模型,同样的方法在第3章已经用过。调用LPD3DXMESH对象的DrawSubset()函数可以绘制模型。DrawSubset()的函数原型如程序清单12.8所示。该函数只有一个参数,即要绘制的网格索引。记住:在X模型文件中可以指定多个网格。在渲染X模型时,如果想在屏幕上渲染整个模型,就要渲染所有的网格。虽然第3章只涉及到一个网格模型,但对X模型而言,可以有多个网格。

    HRESULT DrawSubset(

      UINT AttribId     // 要绘制的网格索引

    );

        

    就像在第3章所做的工作一样,必须牢记一点:一定要调用对象的Release()函数释放所有用到的内存。

    Model Loading 演示程序


       首先,从main源文件的全局部分入手。这一部分包含了常用的对象,还添加了Direct3D光照对象、网格、材质总数、LPD3DXBUFFER缓存、材质链表以及模型使用的纹理链表等内容。光照对象和网格对象很容易理解,而且在前面的章节已经做过介绍。用于保存材质总数的全局变量可以得到定义在模型文件中的材质数量。LPD3DXBUFFER对象从X模型文件中获取材质数据,这样就可以在代码中施加材质。材质链表指明了从该模型文件可以创建的真正材质,而纹理链表指明了施加给每个网格的纹理对象。X Model Loading 演示程序全局部分完整代码如程序清单12.9所示。

           程序清单12.9 X Model Loading 演示程序完整的全局部分

    #include<d3d9.h>

    #include<d3dx9.h>

    #pragma comment(lib, "d3d9.lib")

    #pragma comment(lib, "d3dx9.lib")

    #define WINDOW_CLASS    "UGPDX"

    #define WINDOW_NAME     "X Model Loading"

    #define WINDOW_WIDTH    640

    #define WINDOW_HEIGHT   480

    #define FULLSCREEN      0

    // Function Prototypes...

    bool InitializeD3D();

    bool InitializeObjects();

    void RenderScene();

    void Shutdown();

    // Global window handle.

    HWND g_hwnd = 0;

    // Direct3D object and device.

    LPDIRECT3D9 g_D3D = NULL;

    LPDIRECT3DDEVICE9 g_D3DDevice = NULL;

    // Matrices.

    D3DXMATRIX g_projection;

    D3DXMATRIX g_worldMatrix;

    D3DXMATRIX g_ViewMatrix;

    // Display object.

    LPD3DXMESH g_model = NULL;

    DWORD g_numMaterials;

    LPD3DXBUFFER g_matBuffer = NULL;

    D3DMATERIAL9* g_matList = NULL;

    LPDIRECT3DTEXTURE9* g_textureList = NULL;

    // Scene light source.

    D3DLIGHT9 g_light;

        

    该演示程序中出现改动的三个函数中的第一个是InitializeObjects()。该函数的工作方式除了加载X模型的代码之外,和前面章节演示程序中的类似。为了在演示程序中加载X模型,首先调用D3DXLoadMeshFromX()函数。然后使用CloneMesh()函数将D3DVERTEXELEMENT9对象复制到网格中。这样做是因为想通过该对象添加比其在文件中多许多的信息而更改网格。在文件中并未指明法线,但使用D3DXComputeNormals()函数就可以计算网格的法线,而不必在文件中指明那些法线。当然,这对大量多边形而言就很慢。但对只有绝对需要使用法线的情况而言,这样做可以节省磁盘空间。由于原始模型不包含法线,因此必须调用包含法线信息的CloneMesh()函数更新对象。

    #include<d3d9.h>
    #include<d3dx9.h>

    #pragma comment(lib, "d3d9.lib")
    #pragma comment(lib, "d3dx9.lib")


    #define WINDOW_CLASS "UGPDX"
    #define WINDOW_NAME "X Model Loading"
    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480
    #define FULLSCREEN 0

    // Function Prototypes...
    bool InitializeD3D();
    bool InitializeObjects();
    void RenderScene();
    void Shutdown();


    // Global window handle.
    HWND g_hwnd = 0;


    // Direct3D object and device.
    LPDIRECT3D9 g_D3D = NULL;
    LPDIRECT3DDEVICE9 g_D3DDevice = NULL;


    // Matrices.
    D3DXMATRIX g_projection;
    D3DXMATRIX g_worldMatrix;
    D3DXMATRIX g_ViewMatrix;


    // Display object.
    // 网格对象
    LPD3DXMESH g_model = NULL;
    // 材质总数
    DWORD g_numMaterials;
    // LPD3DXBUFFER缓存,用于从X模型文件中获取材质数据,这样就可以在代码中施加材质.
    LPD3DXBUFFER g_matBuffer = NULL;
    // 材质链表,指明了从该模型文件可以创建的真正材质.
    D3DMATERIAL9* g_matList = NULL;
    // 纹理链表,指明了施加给每个网格的纹理对象.
    LPDIRECT3DTEXTURE9* g_textureList = NULL;

    // Scene light source.
    D3DLIGHT9 g_light;


    LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    switch(msg)
    {
    case WM_DESTROY:
    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    break;

    case WM_KEYUP:
    if(wParam == VK_ESCAPE) PostQuitMessage(0);
    break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
    }


    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prevhInst, LPSTR cmdLine, int show)
    {
    // Register the window class
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
    GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
    WINDOW_CLASS, NULL };
    RegisterClassEx(&wc);

    // Create the application's window
    HWND hWnd = CreateWindow(WINDOW_CLASS, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
    100, 100, WINDOW_WIDTH, WINDOW_HEIGHT,
    GetDesktopWindow(), NULL, wc.hInstance, NULL);

    // Show the window
    ShowWindow(hWnd, SW_SHOWDEFAULT);
    UpdateWindow(hWnd);

    // Record for global.
    g_hwnd = hWnd;

    // Initialize Direct3D
    if(InitializeD3D())
    {
    // Enter the message loop
    MSG msg;
    ZeroMemory(&msg, sizeof(msg));

    while(msg.message != WM_QUIT)
    {
    if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }
    else
    RenderScene();
    }
    }

    // Release any and all resources.
    Shutdown();

    // Unregister our window.
    UnregisterClass(WINDOW_CLASS, wc.hInstance);
    return 0;
    }


    bool InitializeD3D()
    {
    D3DDISPLAYMODE displayMode;

    // Create the D3D object.
    g_D3D = Direct3DCreate9(D3D_SDK_VERSION);
    if(g_D3D == NULL) return false;

    // Get the desktop display mode.
    if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode)))
    return false;

    // Set up the structure used to create the D3DDevice
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    if(FULLSCREEN)
    {
    d3dpp.Windowed = FALSE;
    d3dpp.BackBufferWidth = WINDOW_WIDTH;
    d3dpp.BackBufferHeight = WINDOW_HEIGHT;
    }
    else
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = displayMode.Format;
    d3dpp.BackBufferCount = 1;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;


    // Create the D3DDevice
    if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
    D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE,
    &d3dpp, &g_D3DDevice))) return false;

    // Initialize any objects we will be displaying.
    if(!InitializeObjects()) return false;

    return true;
    }


    bool InitializeObjects()
    {
    // Set default rendering states.
    g_D3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
    g_D3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    g_D3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);


    // Setup the g_light source and material.
    g_light.Type = D3DLIGHT_DIRECTIONAL;
    g_light.Direction = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

    D3DCOLORVALUE white;
    white.a = white.r = white.g = white.b = 1;

    g_light.Diffuse = white;
    g_light.Specular = white;

    g_D3DDevice->SetLight(0, &g_light);
    g_D3DDevice->LightEnable(0, TRUE);

    /*
    使用CloneMesh()函数将D3DVERTEXELEMENT9对象复制到网格中。
    这样做是因为想通过该对象添加比其在文件中多许多的信息而更改网格。
    在文件中并未指明法线,但使用D3DXComputeNormals()函数就可以计算网格
    的法线,而不必在文件中指明那些法线。当然,这对大量多边形而言就很慢。
    但对只有绝对需要使用法线的情况而言,这样做可以节省磁盘空间。由于原
    始模型不包含法线,因此必须调用包含法线信息的CloneMesh()函数更新对象。

    加载完模型后,就可以得到材质信息并加载纹理。通过将LPD3DXBUFFER对象的
    材质复制到材质链表中即可完成该工作。因为LPD3DXBUFFER对象还包含了纹理名称,
    所以也可以加载纹理,这些纹理就在缓存中每种材质的后面。有了这些内容,
    InitializeObjects()函数的其他部分跟往常一样继续下去。程序清单12.10给出了完整
    的InitializeObjects()函数。

    在创建顶点元素(D3DVERTEXELEMENT9)时,偏移的字节就是顶点结构中标识数据开始位置
    的字节数。所以如果第一个元素是顶点位置,那么该偏移量就是0,因为该元素前面没有
    任何元素。如果第二个元素是法线,那么偏移量为12,因为第一个元素占用了12个字节。
    如果第三个元素是一组纹理坐标,那么偏移量为24,这是因为每个浮点数占4个字节。
    由于在处理位置时有三个浮点值,因此总共需要12个字节。例如,如果在纹理坐标后面是颜色,
    那么偏移量为32,因为第一个元素占12个字节,法线占12个字节,纹理坐标占8个字节。

    D3DVERTEXELEMENT9
    Defines the vertex data layout. Each vertex can contain one or more data types, and each data
    type is described by a vertex element.

    typedef struct D3DVERTEXELEMENT9
    {
    WORD Stream;
    WORD Offset;
    BYTE Type;
    BYTE Method;
    BYTE Usage;
    BYTE UsageIndex;
    } D3DVERTEXELEMENT9, *LPD3DVERTEXELEMENT9;

    Members
    Stream
    Stream number.
    Offset
    Offset from the beginning of the vertex data to the data associated with the particular data type.
    Type
    The data type, specified as a D3DDECLTYPE. One of several predefined types that define the data size.
    Some methods have an implied type.
    Method
    The method specifies the tessellator processing, which determines how the tessellator interprets
    (or operates on) the vertex data. For more information, see D3DDECLMETHOD.
    Usage
    Defines what the data will be used for; that is, the interoperability between vertex data layouts
    and vertex shaders. Each usage acts to bind a vertex declaration to a vertex shader. In some cases,
    they have a special interpretation. For example, an element that specifies D3DDECLUSAGE_NORMAL or
    D3DDECLUSAGE_POSITION is used by the N-patch tessellator to set up tessellation. See D3DDECLUSAGE
    for a list of the available semantics. D3DDECLUSAGE_TEXCOORD can be used for user-defined fields
    (which don't have an existing usage defined).
    UsageIndex
    Modifies the usage data to allow the user to specify multiple usage types.

    */
    // 新mesh的顶点声明
    D3DVERTEXELEMENT9 elements[] =
    {
    { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, // 位置
    { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, // 法线
    { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, // 纹理坐标
    D3DDECL_END()
    };

    // Load mesh into temp object then copy over (to set elements).
    LPD3DXMESH temp = NULL;
    if(FAILED(D3DXLoadMeshFromX("Model.x", D3DXMESH_SYSTEMMEM,
    g_D3DDevice, NULL, &g_matBuffer, NULL,
    &g_numMaterials, &temp))) return false;
    //elements:顶点声明
    //g_model:Address of a pointer to an ID3DXMesh interface, representing the cloned mesh.
    temp->CloneMesh(D3DXMESH_SYSTEMMEM, elements, g_D3DDevice, &g_model);
    if(temp) temp->Release();

    // Calculate normals for lighting.
    D3DXComputeNormals(g_model, NULL);

    // Allocate the lists for materials and textures.
    g_matList = new D3DMATERIAL9[g_numMaterials];
    g_textureList = new LPDIRECT3DTEXTURE9[g_numMaterials];

    // Get a pointer to the buffer
    D3DXMATERIAL* mat = (D3DXMATERIAL*)g_matBuffer->GetBufferPointer();

    // Loop and load each textture and get each material.
    for(DWORD i = 0; i < g_numMaterials; i++)
    {
    // Copy the materials from the buffer into our list.
    g_matList[i] = mat[i].MatD3D;

    // Load the textures into the list.
    if(FAILED(D3DXCreateTextureFromFile(g_D3DDevice,
    mat[i].pTextureFilename,
    &g_textureList[i])))
    g_textureList[i] = NULL;
    }


    // Set the projection matrix.
    D3DXMatrixPerspectiveFovLH(&g_projection, D3DX_PI / 4,
    WINDOW_WIDTH/WINDOW_HEIGHT, 0.1f, 1000.0f);

    g_D3DDevice->SetTransform(D3DTS_PROJECTION, &g_projection);


    // Define camera information.
    D3DXVECTOR3 cameraPos(0.0f, 0.0f, -10.0f);
    D3DXVECTOR3 lookAtPos(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 upDir(0.0f, 1.0f, 0.0f);

    // Build view matrix.
    D3DXMatrixLookAtLH(&g_ViewMatrix, &cameraPos,
    &lookAtPos, &upDir);

    return true;
    }


    void RenderScene()
    {
    // Clear the backbuffer.
    g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
    D3DCOLOR_XRGB(0,0,0), 1.0f, 0);

    // Begin the scene. Start rendering.
    g_D3DDevice->BeginScene();

    // Apply the view (camera).
    g_D3DDevice->SetTransform(D3DTS_VIEW, &g_ViewMatrix);

    // g_numMaterials个网格 每个网格对应一个材质,、
    // 对每个mesh应用材质和纹理,然后渲染
    // Draw the model.
    for(DWORD i = 0; i < g_numMaterials; i++)
    {
    g_D3DDevice->SetMaterial(&g_matList[i]);
    g_D3DDevice->SetTexture(0, g_textureList[i]);

    g_model->DrawSubset(i);
    }

    // End the scene. Stop rendering.
    g_D3DDevice->EndScene();

    // Display the scene.
    g_D3DDevice->Present(NULL, NULL, NULL, NULL);
    }


    void Shutdown()
    {
    if(g_D3DDevice != NULL) g_D3DDevice->Release();
    g_D3DDevice = NULL;

    if(g_D3D != NULL) g_D3D->Release();
    g_D3D = NULL;

    if(g_model != NULL) g_model->Release();
    g_model = NULL;

    for(DWORD i = 0; i < g_numMaterials; i++)
    {
    if(g_textureList[i] != NULL)
    {
    g_textureList[i]->Release();
    g_textureList[i] = NULL;
    }
    }

    if(g_matList != NULL)
    {
    delete[] g_matList;
    g_matList = NULL;
    }

    if(g_textureList != NULL)
    {
    delete[] g_textureList;
    g_textureList = NULL;
    }

    if(g_matBuffer != NULL)
    {
    g_matBuffer->Release();
    g_matBuffer = NULL;
    }
    }

    /*
    一个X文件不包含顶点法线数据,这是很有可能的。假如是这种情况,
    那么手动计算顶点法线以便我们能够使用灯光这是很有必要的。现在
    知道了ID3DXMesh接口和它的父接口ID3DXBaseMesh,我们能够使用下面
    的函数来产生任何mesh的顶点法线:

    Computes unit normals for each vertex in a mesh. Provided to support legacy applications.
    Use D3DXComputeTangentFrameEx for better results.

    HRESULT D3DXComputeNormals(
    LPD3DXBASEMESH pMesh,
    CONST DWORD * pAdjacency
    );
    */



    展开全文
  • 在上一篇文章中,我们导入了Mixamo生成的人物骨骼模型和动画文件,但是,这里的人物模型只能使用Mixamo导入的动画,而不能使用UE4的动画包。这是因为两者骨骼并不相同,如图,需要进行重定向。第一步 添加root节点...

    在上一篇文章中,我们导入了Mixamo生成的人物骨骼模型和动画文件,但是,这里的人物模型只能使用Mixamo导入的动画,而不能使用UE4的动画包。这是因为两者骨骼并不相同,如图,需要进行重定向。


    第一步 添加root节点(非必须)

    比较两者的骨骼树,发现UE4的官方骨骼文件,有root节点。给Mixamo生成的人物模型添加root节点方法参考https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/FixingMixamoRootMotionWithPython.md,在此表示感谢

    1.1 安装UnrealEnginePython插件

    首先,前往https://github.com/20tab/UnrealEnginePython/releases下载符合所使用引擎版本的UnrealEnginePython插件,推荐embedded版本,方便安装使用,例如,本人使用4.18版本的引擎,选择UnrealEnginePython_20180330_4_18_python36_embedded_win64.zip。

    此处,建议关闭Unreal Editor。在工程目录文件下新建Plugins目录,此目录与Content目录同级,将压缩包在此目录下解压。例如,工程名为FPS,则解压后有,RPGProject\Plugins\UnrealEnginePython

    打开Unreal Editor,点击窗口,最下方出现Python Editor


    点击,可以打开一个python的编辑器

    1.2 运行Mixamo.py

    https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/FixingMixamoRootMotionWithPython_Assets/mixamo.py下Copy代码,打开Python Editor,选择new,新建一个脚本文件,粘贴复制的代码。若右下角弹出import窗口,选择确定import。

    保持Python Editor打开,在Unreal内容浏览器中选择之前导入的Mixamo骨骼网格物体,然后,单击Python Editor的Execute,如图所示


    弹出保存窗口,选择保存位置,执行结束后,多出两个文件,如图


    这两个文件即是添加root节点后的骨骼文件,可以双击查看

    第二步 骨骼重定向

    2.1 UE4_Mannequin_Skeleton的Rearget

    首先,双击UE4自带的UE4_Mannequin_Skeleton骨骼文件


    单击左上角的Retarget Manager,再弹出的对话框里,选择类人绑定,如图


    然后单击下方的Auto Mapping按钮,自动完成映射,点击保存,后关闭

    2.2 Mixamo导入模型的Retarget

    双击导入的骨骼模型,同样,选择Retarget Manager,选择类人绑定,但是这里不能Auto Mapping,需要手动进行绑定。UE4与Mixamo绑定的对应关系如下图所示。

    基本



    高级




    完成手动绑定,点击保存,后关闭

    2.3 重利用动画

    选择一个官方的动画,右击,选择重定向动画资源


    弹出如下画面,此处应该保持两者预览画面的姿势一致,同为T-pose或A-pose之类的(当使用UE4.1X版本时,可能出现没有预览画面,且映射失败的情况,在另一篇文章里说明


    点击重定向,则会生成重定向后的动画,拖入Level中,播放正常


    到这里,已经可以使用其他骨骼的动画,但是,因为重定向pose的原因,可能导致模型动画显示的略有问题,下一篇文章,将使用相对简单的方法,调整重定向pose

    展开全文
  • 今年是第二次参加UnrealOpenDay活动,收获颇丰,也认识了很多业界大咖。这里稍微详细点的总结一下会议内容和参会心得。 首先是行程: 会议9点签到,9:45正式开始。地点和去年一样还是在宝华万豪酒店,位于上海马戏...

    今年是第二次参加UnrealOpenDay活动,收获颇丰,也认识了很多业界大咖。这里稍微详细点的总结一下会议内容和参会心得。


    首先是行程:

    会议9点签到,9:45正式开始。地点和去年一样还是在宝华万豪酒店,位于上海马戏城地铁站旁边,从火车站到酒店也就半个多小时,很方便。

    今年可能是因为会议持续两天,会场变大等原因增加了活动预算,所以既没有衣服也没有午餐(附近有一个商场)。

    23号当天有三个会场,24号有两个会场,官方会在会议前几天公布会议流程,由于三个会场同时进行,所以需要自己规划好时间。会议内容如下图,
    这里写图片描述

    有几点需要注意一下

    1. 中午时间非常紧,吃饭要排队的,建议听完后立刻就去吃饭
    2. 有一些演讲可能超时,也许会影响到你的计划
    3. 有一些内容对你可能没什么用,另一些看起来没用还可能有意外收获,该换就换

    另外会场有一些展台可以去体验游戏(我试玩了失落之魂,感觉还可以),和去年相比最大的变化就是VR游戏基本上都没了。

    会议内容总结:这个是我事先规划好的参会行程,稍微有一点调整,下面就我听到的内容简单给大家总结一下。
    这里写图片描述


    2018.5.23

    9:45-10:45 会场A OpenSpeech&KeyNote

    全球虚幻开发者截止2018年3月已经到达500万,最近一年虚幻制作出了很多火爆的游戏如堡垒之夜,绝地求生,绝地求生手游等,优秀的独立游戏也越来越多。堡垒之夜很快要实现全平台发布,任何平台的玩家都可以匹配到一起进行游戏,同时Epic会把优化内容添加到引擎里面。除了在游戏领域,UE还广泛应用在汽车展示,建筑行业,航空航天,影视制作(武庚纪,斗罗大陆等动漫),AR(苹果2017发布会展示的The Machines),真实人物模拟渲染(Siren)等。

    10:45-11:30 会场B 腾讯Wetest

    Wetest是腾讯推出的一个一站式测试平台,提供了包括可视化的性能监测,数据分析,自动采集日志,准确定位单个用户操作信息,机器人模拟运行等功能,个人觉得这些东西对游戏的上线与测试确实非常有用,特别是各个流程都不完善的中小团队,不过价格与实际的使用体验还要亲自测试才行。
    这里写图片描述
    这里写图片描述
    这里写图片描述
    11:30-12:00 会场B 影视制作圆桌会议

    13:00-13:30 会场A 加勒比海盗手游海洋系统

    主要是讲了海洋系统需要考虑的一些方面,并给出了实现与优化策略。这里挑几点说一下,

    1.波浪动画 为了避免游戏进行中大量的实时计算,他采用了FFT算法提前将水体的运动轨迹数据计算并保存到贴图里面,然后在运行时读取这些数据,这种方式稍微增加了内存开销。同时参考了刺客信条里面海洋的实现方式对具体细节做了一些优化
    这里写图片描述
    这里写图片描述
    2.海洋的光照模型采用GGX舍弃Phong

    3.材质方面采用平面反射,细节法线与次表面散射提升画面表现。泡沫使用的是预生成的蒙版图。浅海水底的焦散方式采用Epic GDC2017的Talk:Content-Driven Multipass Rendering inUE4,也是事先计算好数据存储到贴图里面
    这里写图片描述
    这里写图片描述
    4.船体与水面交互 在船的周围放上几个球形检测体,通过计算球形体与水面的位置等执行交互逻辑

    5.船尾的波纹如何实现 为了表现乘风破浪的效果,他利用粒子系统生成相应的高度图来产生海面的交互起伏

    13:30-14:00 会场C PSVR

    去的晚了,只听到一部分,他通过修改RHI的API利用PS4专有的硬件加速手段开启了PS4上面的MSAA,提升了清晰度。另外,在渲染上将画面分成了几个区域分别处理,提升了帧率。
    这里写图片描述

    14:00-15:30 会场A 堡垒之夜性能优化

    主要讲了手游上如何在尽量不影响玩家体验的情况下优化系统,包括声音优化,物理优化,渲染优化,材质大小,资源加载优化等。Epic测试了不同机型的各方面的测试数据,提出来手游优化很艰辛(特别是安卓)。。。

    • 增加了针对手游(各个平台)更为细致的scalability层级划分,可以在不同的平台上更细致的调整特性。简单来说就是在不行的机器上能砍掉就砍掉,砍不掉就想办法砍掉一部分(稍微皮一下)
    • IOS上,新增了三个Task线程来处理并行动画,粒子模拟,物理,贴图的流式加载,场景查询,渲染裁剪等,另外,还增加了一个Audio线程。对于安卓机型,新增了RHI线程
    • 通过距离,Occlusion裁剪,层级Lod,HLOD,动态阴影等方式减少DrawCall
    • 地形上Lod也做了优化,使远近的LOD面数差距更明显,性能提升效果更明显

    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述
    15:35-16:05 会场B Incredibuild分布式编译

    Incredibuild可以大大提高编译,烘焙,shader的速度,看数据确实很诱人。根据服务器数量,配置等按照时间收费。好像是8台8核机器,一年1000美元?
    这里写图片描述

    16:05-16:15 会场C 本地化

    我原来以为是分析引擎本地化原理与流程,其实就是简单介绍UE官方文档方面的本地化流程。

    16:15-16:45 会场C VR方舟公园性能优化

    • 粒子优化 不在发射器里面加光照组件,少用GPU粒子,
    • 使用Unlit着色模型,如果使用DefaultLit着色模型,LightMode选用VolumetricPerVertex NonDirection shader 使用Mask材质尽量不用透明材质
    • 设置合适的裁剪距离与LOD,使用Precomputed
    • Visibility来减少硬件遮挡查询的消耗,采用渲染实例化工具来降低渲染批次(可以自己写实例化工具处理植被以外的对象) 光照尽量使用静态光源,其次是半动态光源,最后再考虑动态光源。动态物件IndirectLightCacheQuality设置为ILCQVolume。不要勾选摆动植被CastDynamicShadow选项
    • 阴影 半动态光源下动态物件均使用Per-ObjectShadow绘制阴影会导致动态物件过多的时候半动态光源比动态光源消耗还大,所以尽量减少动态物件的使用。渲染阴影选用LOD级别较低的渲染
    • 后处理 静态、半动态光源,推荐使用的后处理有Distortion,TemporalAA,Tonemapper,Bloom。动态光照相比前面加一个SSAO。
    • 采用NVIDIA的VRWorks优化VR 但VRWorks不支持胶囊体阴影,距离场AO,还有场景边缘不清晰的问题。例外,MS与SPS只适用于Pascal架构的显卡
    • 前向渲染+TAA的效果比较好 Draw线程优化
    • 使用PrecomputedVisibility,减少视频播放,减少参与计算动态物件的光源数量 使用VRworks-Single Pass Stereo合并左右眼渲染重复计算的部分
    • 碰撞 不需要事件的对象不要勾选GenerateOverlapevents,减少物理资产碰撞体数量,只需要射线检测的不需要完全开启CollisonEnabled
    • 动画 更新模式选择OnlyTickPoseWhenRendered

    这里写图片描述
    这里写图片描述

    16:45-17:05 会场B Unreal大地形加载

    总结来说就是讲解如何把Unity里面的数据移植到Unreal并做了一下性能对比,发现Unreal性能基本上完胜Unity(吃惊)

    之前他们在网上有一篇文章,很详细,参考链接:https://zhuanlan.zhihu.com/p/36731312

    17:15-18:05 会场B 跨平台VR

    18:05-18:35 会场B 区块链与游戏

    简单讲解了区块链的概念与基本原理,展望了一下区块链在游戏行业的未来。感觉大家热情不是很高,对区块链的认识还是有一定的限制吧


    2018.5.24

    09:45-10:35 会场B 编辑器扩展

    讲解非常全面,从菜单栏到工具栏再到配置选项,蓝图节点。基本上你在编辑器上的所有操作都涉及到了,不过内容太多导致很多细节不够,API名称太多也记不过来(不过听说有PPT)。

    10:35-11:05 会场B 救赎之路

    主美:展示了大量的作品与草稿。总结一句,创作要靠感觉~
    制作人:项目立项遇到了游戏体验差时间与资金不足的问题,然后及时中断调整
    主程:手动计算锁链动画位置解决原生模拟效果不好的问题,通过CustomMask实现传送门场景的效果。
    这里写图片描述
    这里写图片描述
    11:05-11:35 会场A 如何渲染出一个真实的人物角色

    Epic的TA ,ZakParrish(非常活泼开朗的一位技美,很羡慕这样的大佬)分享了像Siren这样一个超级真实的人物是怎样用虚幻引擎渲染出来的。(然而水平有限,并没有都跟上)

    • 基本的面部数据是通过扫描得来的 头部4万个左右的三角形
    • 头发使用xgen工具创建。分为四个部分,包括基本的轮廓模型可以飘动的细节模型等,有三张UV贴图。对于真实的角色,面部的模型走向,发型走向是非常重要的
    • 调整了牙齿与嘴唇的边界过度问题,眼部增加了虹膜阴影 使用cavitymaps增加面部细节。
    • 绒毛采用半透明材质,根部是颜色深,头部发亮

    这里写图片描述
    这里写图片描述

    11:35-12:05 会场A 失落之魂动作表现

    • 刀光 第一种是AnimaTrail粒子(再通过材质系统控制Alpha通道),第二种是Faketrail 打击效果一个简单的击中效果就由刀光,血雾,碎片等多个特效组成,很多情况要配置后处理实现(Blur)
    • 残影 使用网上的插件GhostMesh,基本原理是在玩家移动的轨迹上创建一个新的Mesh
    • 辅助效果 钝帧Dilation,手柄力反馈

    13:00-14:00 会场A 构建高端表现效果的粒子

    现场讲解并操作了一个激光枪打到地面上实现灼烧以及周围冷却凝固的效果。

    14:00-15:10 会场A 手游大世界(刺激战场)

    通过流关卡切换高空与落地时不同的植被模型

    将地形数据拆成单独的Level加载,通过地形Lod系数等降低精度,采用镂空简单模型覆盖在真实地形外围来减少DrawCall
    这里写图片描述

    不同的level存放不同的lod,根据配置距离加载不同的level进而加载不同的模型Lod

    采用离线烘焙减少内存与包体,提升工作效率

    阴影优化 包括FarFadeOut,层级间软过度,HardWarePCF提升采样性能,CSM阴影缓存,MoreCSMStable,指令优化,不同材质采样书目调整

    抗锯齿 MSAA+AlphaToCoverage

    带宽优化 HDR rendertarget选择 RGBA16F或R11G11B10f,IOS下剔除了Stencil
    这里写图片描述
    这里写图片描述
    15:10-15:40 会场B 光明记忆

    分享了蓝图下Gameplay框架与相关技能的实现

    15:40-16:10 会场B 在线开放世界

    主要讲述了大世界场景地图的工作流是什么样的
    这里写图片描述

    16:10-16:40 会场B 探索VR的无限可能

    16:40-18:10 会场A 新的粒子系统Niagara

    听到了一点Maya与UE的livelink,可以实现Maya到UE的单向数据传输。

    4.20版本会新增粒子系统Niagara,相比Cascade粒子系统多了时间轴编辑与蓝图功能,可以在Niagara内部调整粒子的运动速度与方向等,总结来说就是可以在不用写代码的情况下实现更多酷炫的粒子效果
    这里写图片描述


    会议相比去年的另一个进步就是三个会场都有全程的视频录制,官方说之后会放出来。如果有的话,我这里会第一时间更新视频链接。

    最后,这次行程最高兴的就是认识了很多牛人,非常感谢知乎 @大钊 组织的聚会!

    展开全文
  • Unreal Engine 4.9 开始 虚幻 4 之旅 本页面的内容: 编辑器 快速词汇查找表 项目文件和文件 从 GameObjects 到 Actors 如何在虚幻 4 中编写代码 在虚幻 4 中编写游戏逻辑代码 常见问题 然后呢? 本指南...
    Unreal Engine 4.9
    
    

    image_0.png

    本指南从一个 Unity 的用户视角来整体看待虚幻 4,并帮助将 Unity 的开发经验应用到虚幻 4 的世界中。

    编辑器

    下面是 Unity 编辑器和虚幻编辑器的图片,用颜色标识了同类的区块。每个区块也添加了标签,对应于虚幻术语表。虚幻编辑器完全可以通过对各个区块的拖拽做到窗口布局的自定义。

    image_1.png

    编辑资源素材

    在 Unity 中,Inspector 分页时用来编辑当前项目中选中的素材。在虚幻 4 中,细节 面板则用来展示并修改当前选中物体的属性,然后大量的编辑修改则需要专用的窗口或分页。编辑每种类型的资源时都会打开分页窗口,类似于网页浏览器。当然这些分页窗口也可以任意拖拽,并悬浮在其他窗口之上作为独立窗口显示。

    image_2.png

    快速词汇查找表

    下表左侧包含了常见的 Unity 术语,右侧是对应的(或差不多的)虚幻 4 术语。虚幻 4 的关键词直接链接到更进一步的虚幻在线文档中。

    分类 Unity 虚幻 4
    游戏内容类型 Component 组件
      GameObject Actor,Pawn
      Prefab 蓝图类
    编辑器界面 Hierarchy Panel 世界大纲
      Inspector 细节面板
      Project Browser 内容浏览器
      Scene View 视口
    网格物体 Mesh 静态网格物体
      Skinned Mesh 骨骼网格物体
    材质 Shader 材质,材质编辑器
      Material 材质实例
    特效 Particle Effect 特效,粒子,级联
      Shuriken 级联
    游戏界面 UI UMG (虚幻示意图行 Unreal Motion Graphics)
    动画 Animation 骨骼动画系统
      Mecanim Persona ,动画蓝图
    2D Sprite Editor Paper2D
    编程 C# C++
      Script 蓝图
    物理 Raycast Line Trace, Shape Trace
      Rigid Body 碰撞,物理
    运行平台 iOS Player, Web Player 支持的平台

    项目文件和文件

    那么目录和文件时怎么回事

    和 Unity 的项目一样,虚幻的项目也存在于自有的目录,并有自己的项目文件。可以通过 双击 一个 .uproject 文件打开虚幻编辑器并加载该项目,或者右键 来查看更多选项。项目目录中包含不同的子目录,保存了游戏的资源内容和源代码,以及各种配置文件和二进制文件。最重要的就是Content 子目录和 Source 子目录。

    我的资源素材应该放在哪里?

    在虚幻 4 中,每个项目都有一个 Content 文件夹。类似于 Unity 项目的 Asset 目录,这里就是游戏资源素材保存的地方。要向游戏中导入素材的话,只需要将素材文件拷贝到 Content 目录,它们便会自动导入并出现在内容浏览器 中。当使用外部程序修改这些资源时,编辑器中的资源也会自动的被更新。

    image alt text

    支持哪些通用资源文件格式?

    Unity 支持很多文件格式。虚幻 4 也支持最通用的文件格式,如下表:

    资源类型 支持的格式
    3D .fbx, .obj
    贴图 .png, .jpeg, .bmp ,.tga, .dds, .exr, .psd, .hdr
    声音 .wav
    字体 .ttf, .otf
    视频 .mov, .mp4, .wmv

    场景是如何保存的?

    在 Unity 中,GameObjects 被放置在场景中,并保存为一个场景资源文件。虚幻有一个地图文件,对应于 Unity 场景。地图文件保存了 关卡 的数据以及其中的物件,以及光照数据和某些关卡特定的设置。

    如果修改项目的设置?

    所有的项目设置都可以在主菜单的 Edit / Project Settings 中找到。类似于 Unity 的设置,能够对项目设定所需要的信息(比如项目的名称和图标),配置游戏输入的绑定,并定义运行项目时引擎的行为。可以在这里 了解更多单独项目的设置。Unity 还有被称为 “玩家设置” 的部分,在虚幻中,我们叫 “平台设置”,并且可以在项目设置的 “平台” 分类里找到。

    源文件在哪里?

    在 Unity 中,人们习惯于将 C# 的源文件放在资源目录中。

    虚幻 4 工作机制有点不同。对那些拥有 C++ 代码的项目,可以在项目目录中找到一个 Source 的子目录包含多种文件,包括 C++ 源文件(.cpp)和头文件(.h),以及编译链接的脚本(.Build.cs,.Target.cs)。然后,只有蓝图的项目则不会有 Source 目录。

    在虚幻 4 中开始使用 C++ 最方便的做法是用编辑器的菜单项来 Add Code To Project(在主菜单的文件菜单中),或应用某个模板来新建一个 C++ 的项目。可以直接在内容浏览器 中直接看到 C++ 类,并通过双击它们便能直接在 Visual Studio 或 Xcode 中打开该文件。

    从 GameObjects 到 Actors

    GameObject 去哪里了?

    在 Unity 中,一个 GameObject 是可以被放置在世界中的一个 “东西”。在虚幻 4 中对应的是一个 Actor。在虚幻编辑器中,可以从物体面板中直接拖一个空的 Actor 放置到场景中:

    Youtube 视频

    虽然可以通过搭建并扩展空的 Actor 来制作游戏,但虚幻 4 提供了各种特殊类型的 Actor 并预制了它们的特性,比如 Pawn(用于作为玩家或者 AI 的角色),或者 Character(用于会做动作的生物)。和空的 Actor 一样,可以直接将它们拖拽至场景中,并给它们添加组件,或自定义属性。之后可以学习到更多相关内容,这里只需要了解虚幻 4 有个 (Gameplay 框架)[(Gameplay/Framework)] 来协同各种特殊的 Actor 一起工作。

    虚幻 4 中的 Actor 还是和 Unity 中的 GameObjects 有不同之处。在 Unity 中,GameObject 是 C# 的类并且无法直接扩展。在虚幻 4 中,Actor 是 C++ 类,可以很容易的被继承或扩展来自定义。我们之后将会谈论更多!

    组件在哪里?

    在 Unity 中,可以通过为一个 GameObject 添加组件来达到给予一定的功能。

    在虚幻 4 中,也可以为 Actor 添加组件。在向关卡中放置了一个空的 Actor 后,点击添加组件按钮(在 细节 面板中),并选择一个组件来添加。这里我们通过放置一个空的 Actor 来创建一个火炬,并为它添加一个网格物体组件作为基座,以及一个光源和粒子系统作为它的火焰。

    Youtube 视频

    在 Unity 中,一个 GameObject 保存了组件的简单列表,但在虚幻 4 中,一个 Actor 保存了属于它的组件以及它们的继承结构关系。可以在上面的例子中看到,光源和粒子是连接到网格模型的。之后在复合 Actor 和 复合 GameObject 中会有重要的描述讨论。

    从 Unity 的 prefabs 到虚幻 4 的蓝图类

    Unity 的工作流程是基于 prefabs 的。在 Unity 中,创建一系列 GameObjects 以及它们的组件,并为它们创建 prefab。然后便可以在世界中放置 prefab 的实例,或者在运行时创建它们。

    虚幻 4 则是基于蓝图类来协同工作。在虚幻 4 中,创建一个 Actor 以及它的组件,选择它并点击 蓝图 / 添加脚本 按钮(在细节 面板中)。然后选择保存蓝图类的地方,并点击创建蓝图 来保存新建的蓝图!

    Youtube 视频

    新建的蓝图类可以在 内容浏览器 中找到。可以直接 双击 打开编辑它们,也可以将它们拖拽到任意场景关卡中。

    脚本组件和 MonoBehaviour 在哪里?

    在 Unity 中,GameObject 有脚本组件,并可以添加 C# 脚本。可以创建 MonoBehavior 子类并定义那个组件做什么。

    虚幻 4 也有类似的内容。可以自由创建全新的组件类并将它应用于任意 Actor。组件类可以使用蓝图脚本创建,也可以用 C++ 创建。

    那么在虚幻 4 中如何创建自己的组件类呢?在 细节 面板中,添加组件(Add Component)的下拉框中,可以看到创建新组件,或者选择已经存在的组件:

    image alt text

    在 Unity 中,当创建新的 MonoBahaviour 时,将会有一个框架类文件,并有 Start() 函数和 Update() 函数。

    在虚幻 4 中,也会有一个框架类,有一个 InitializeComponent() 函数和一个 TickComponent() 函数,它们和 Start、Update 具有类似的行为。

    如果创建一个蓝图脚本组件,则会有可视化节点来表示类似的函数:

    image alt text

    可编辑脚本 Actor 蓝图类

    这是个很酷的虚幻 4 功能:新建的 Actor 蓝图类可以拥有自己的可视化蓝图脚本编辑!这样能够为整个物体添加逻辑,而不仅仅是每个组件。结合继承结构关系(稍后下文解释),这将会提供游戏制作很多灵活性。

    In addition to Blueprint Classes supporting visual scripting, UE4 also supports C++ Classes implemented with code. Here are both, side-by-side. 除了蓝图支持可视化脚本编辑,虚幻 4 还支持通过代码 C++ 类来实现。这里是双方的一个对照:

    Unity C# 虚幻 4 C++
    using UnityEngine;
    using System.Collections;
    
    public class MyComponent : MonoBehaviour
    {
        int Count;
    
        // Use this for initialization.
        void Start ()
        {
            Count = 0;
        }
    
        // Update is called once per frame.
        void Update () 
        {
    
            Count = Count + 1;
            Debug.Log(Count);
        }
    }
    #pragma once
    #include "GameFramework/Actor.h"
    #include "MyActor.generated.h"
    
    UCLASS()
    class AMyActor : public AActor
    {
        GENERATED_BODY()
        int Count;
    
        // Sets default values for this actor's properties.
        AMyActor() 
        {
            // Allows Tick() to be called
            PrimaryActorTick.bCanEverTick = true;  
        }
    
        // Called when the game starts or when spawned.
        void BeginPlay()
        {
            Super::BeginPlay();
            Count = 0;
        }
    
        // Called every frame.
        void Tick(float DeltaSeconds)
        {
            Super::Tick(DeltaSeconds);
            Count = Count + 1;
            GLog->Log(FString::FromInt(Count));
        }
    };
    虚幻 4 蓝图
    image_28.png

    虚幻 4 蓝图类的扩展性

    Unity 的 prefabs 和虚幻 4 的蓝图类在游戏中类似的实例化。然后 Unity 在 prefabs 之间的关系上有并发的问题,这限制了创作的扩展性。

    在虚幻 4 中,可以通过扩展已有的蓝图类来创建新的蓝图类,并定义新的属性,组件功能及可视化脚本功能。

    比如,在虚幻 4 中,可以创建一个蓝图类叫做 Monster,实现基本的怪物功能,比如追击人类。然后可以创建一个叫做 Dragon 的蓝图类来扩展它(某种特定的怪物,添加了火焰吐息的功能),再有一个 Grue(一种当它变黑是就有可能吃人的怪物),以及其他 8 种类型。这样一些 Monster 的子类都继承了基础的 Monster 类的功能,并在此基础上添加新的能力。

    在 Unity 中,则需要创建很多不同的 GameObject 的 prefabs:为 Dragon 创建一个,为 Grue 创建一个,等等。假设这时希望为所有的怪物添加某个功能,比如使用一个 Speak 组件来说话,在 Unity 中则需要更新所有的 10 个 prefabs,拷贝粘贴到每个中。

    在虚幻 4 中,只需简单的修改 Monster 的蓝图类,并为它添加新的 Speak 的能力,便做完了!Dragon,Grue 以及其他 8 种 Monster 的子类都会自动的继承这个说话的新功能,并不需要去修改这些子类。

    但还有更多!我们关于蓝图类所说的一切,都同样适用于 C++ 的类,也同样对 Actors 和 组件 适用。我们的体系设计时考虑支持各种不同程度的开发形式,具有功能上的可扩展性,可以为 10 个开发人员的项目服务,也可以为 100 个人的项目人员服务。

    那么应该用蓝图脚本还是 C++ 代码呢?或者同时使用?

    可视化蓝图脚本是对游戏实时逻辑和序列型的事件的理想制作手段。这个系统对策划、美术以及面向可视化编程的程序员是非常好用的,它能够可视化的管理游戏内的物体,并且容易访问。完全可以通过蓝图就完成一个游戏的制作。请参考 Tappy Chicken 示例,它是一个完整的范例。

    C++ 编程可以为更大体量的任务工作,比如构建一个游戏体系,制作复杂的 AI 行为,或者新增引擎功能。对于已有 C++ 技能的开发人员而言,可以翻阅一下 在虚幻 4 中的 C++ 编程简述 页面。

    大部分的项目都将是混合使用蓝图和 C++。很多开发者使用蓝图来创作游戏原型,因为这个过程容易且有趣,但会在之后的性能调整和更严格的引擎使用时将部分蓝图或全部蓝图脚本转移至 C++。

    蓝图类也能扩展 C++ 类

    虚幻 4 的游戏开发中很多令人着迷的过程来存在于程序员用 C++ 实现新的功能,而策划和美术在蓝图中使用这些功能,并提出更多要求!下图是针对一个虚幻 4 的射击游戏项目中实现拾取物品过程时,团队的一种形式,混合了 C++ 类的编程实现,以及蓝图类用于处理行为和表现。

    image alt text

    转换组件

    在 Unity 中,每个 GameObject 都有一个转换组件(Transform Component),赋予该 GameObject 在世界中的位置、角度以及缩放比例。

    在 虚幻 4 中类似,Actor 有一个 Root Component,能够作为场景组件的任意子类,场景组件 赋予 Actor 在世界中的位置、角度及缩放比例,这些参数会费赋予该 Actor 的组件关系结构中在它之下的所有组件。很多有用的组件都是场景组件的子类,因此让它们具有位置信息是非常有用的!

    即便只放置一个空的 Actor,虚幻 4 也会为它创建一个“默认场景 Root 组件”,这是一个最简单的场景组件。当放置一个新的场景组件时,默认场景 Root 则会被替换掉。

    复合物体

    在 Unity 中,可以构建 GameObject 之间的关系并将他们的转换组件绑定,来创建复合物体。

    image alt text

    在虚幻 4 中,通过整洁的组件继承便能创建复合游戏物体。

    image alt text

    从上图可以看到,一个整洁的继承关系可以通过将场景组件互相附加来得到,由于他们有转换功能,类似于 Unity 中的转换的绑定。Actor 的各个组件(所有组件的基类)只能直接依附到 Actor 自己身上。

    我是否应该用组件来创造其他一切?

    其实这完全取决于你自己,大部分情况下应该结合 Actor 的类和自定义组件。我们先前已经提到过,虚幻 4 中已经提供了一些特殊类型的 Actor 用于某种能力并总是附带某些组件。比如一个Character 总是会包含一个Character Movement 组件

    有一些在引擎中常见的 Actor 类型,并且大部分种类的游戏都会用到它们。这里是我们已经制作的最常见类型 Actor 的列表:

    • Pawn - Actor 的一种类型,用于表现一个可供控制的游戏物体,比如是玩家的角色。Pawn 通常都是被玩家或者 AI 通过 Controller 控制并移动。

    • Character - 一种特殊类型的 Pawn,用于双足类型的角色,并具备一些复杂的功能。

    • Controller - 依附并控制一个 Pawn。通过将 Pawn 和 Controller 的分离,我们可以编写 AI Controller,用于控制 Pawn,并且和玩家控制 Pawn 采用相同的接口。

    • Player Controller - 一个更为特殊的 Controller,用于从玩家的手柄中获得输入信息,或者鼠标键盘中获得殊瑞星纳西,并将这些信息驱动它所控制的 Pawn 或者 Character 的行为。

    那么所有的东西都是 Actor 咯?

    并不是所有的。Actor 是虚幻 4 中最常见的用于游戏的类,并是唯一能够在 世界 中被创建生成 Spawn 的类。因此任何被放置在关卡中的东西都将是一个 Actor。

    另外一个需要知道的重要类型是 Object。Object 实际上是所有虚幻引擎的类的基类,包括 Actor 以及一些其他的类。这是一个比 Actor 更加底层的类,具备一些作为虚幻引擎类的可预料的功能,比如反射序列化。Object 是一个非常基础的类,当我们需要定义一个新的类但又并非 Actor 的时候会使用它。比如 Actor Component 是所有组件的基类,而它则是继承 Object 而非 Actor。

    在虚幻 4 中 Gameplay 框架是什么东西?

    好吧,这里开始事情变得一点点疯狂(酷炫的方向)。Unity 提供了一个干净的石板用于设计游戏,虚幻也做了同样的事情。在 Unity 中可以基于 GameObjects 和组件来创建所有的东西,而在虚幻中则是基于 Actor 和组件来创建。

    然后,虚幻在顶层提供了叫做 Gameplay 框架 的部分,而 Unity 中并没有这块内容。虽然做一款游戏并不是一定要用到它,但如果用的话会非常酷!基本上来说,它提供了一些基础功能,如果遵循它的逻辑,您将能够很容易的获得一些很赞的功能,否则可能花费很多时间,并且实现也很困难,或者很难改造。(比如完整的多人游戏支持!)

    已有大量的酷炫游戏基于虚幻的 Gameplay 框架之上制作,花点时间来了解该框架的机制是很值得的。没错,最终您将具有您自己的框架形式,如果想要这么做当然完全没问题!但虚幻 4 当前已有数以百计的炫酷开发者在使用它,因此我们花点时间来了解一下。

    要使用 Gameplay 框架,只需要了解一些预制的 Actor 类,比如 PawnCharacter,和Player Controller,并逐步了解虚幻的网络复制和网络其他功能。现在我们先回到最基本的部分。

    如何在虚幻 4 中编写代码

    我曾习惯于在 MonoDevelop 中写代码

    对于蓝图脚本,只需要虚幻编辑器——所有的东西都已经包括了!要编写 C++ 代码,在 Windows 平台下载 Visual Studio 的免费版本,如果是 Mac 的话则需要安装 Xcode。当第一次创建一个新项目(或者为已有项目添加代码)时,虚幻 4 将会自动的创建 Visual Studio 的项目文件。您只需要在内容浏览器 中双击一个 C++ 的类便能在 Visual Studio 中打开它,或者在主菜单的文件菜单中点击Open Visual Studio

    image alt text

    在虚幻 4 中有个重要的不同:有时必须要手动的更新 Visual Studio 的项目文件(比如,下载了 UE4 的新版本,或者对源代码文件的磁盘存放位置做了人为改变)。可以通过在主菜单中点击Refresh Visual Studio Project 或者在项目目录中右键点击 .uproject 文件 并选择Generate Visual Studio project files 便可。

    image alt text

    编写事件函数(Start,Update 等)

    如果您曾用 MonoBehaviors 工作,那一定熟悉诸如 Start,Update 和 OnDestroy 等方法。以下是对 Unity 的行为和对应的虚幻 4 的 Actor 和组件的比较。

    在 Unity 中,我们可能有一个简单的组件,类似下面的代码:

    public class MyComponent : MonoBehaviour
    {
        void Start() {}
        void OnDestroy() {}
        void Update() {}
    }

    请记住,在虚幻 4 中,您可以直接为 Actor 写代码而无需创建新的组件来添加代码。这其实是很常见并有意义的。

    类似于 Unity 的 Start,OnDestroy 和 Update 函数,虚幻 4 在 Actor 中有类似的方法:

    C++

    UCLASS()
    class AMyActor : public AActor
    {
        GENERATED_BODY()
    
        // Called at start of game.
        void BeginPlay();
    
        // Called when destroyed.
        void EndPlay(const EEndPlayReason::Type EndPlayReason);
    
        // Called every frame to update this actor.
        void Tick(float DeltaSeconds);
    };

    蓝图

    image_29.png

    在虚幻 4 中,组件具有不同的函数。以下是示例:

    C++

    UCLASS()
    class UMyComponent : public UActorComponent
    {
        GENERATED_BODY()
    
        // Called after the owning Actor was created
        void InitializeComponent();
    
        // Called when the component or the owning Actor is being destroyed
        void UninitializeComponent();
    
        // Component version of Tick
        void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction);
    };

    蓝图

    image_30.png

    请记住,在虚幻 4 中调用基类的方法很重要。

    比如,在 Unity C# 中可能是调用 base.Update(),但在虚幻 4 的 C++ 中我们使用 Super::TickComponent():

    void UMyComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
    {
        // Custom tick stuff here
        Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    }

    You may have noticed some things begin with "A" and others with "U" in C++. The prefix "A" indicates an Actor sub-class. Where as the prefix "U" indicates anObject sub-class. There are some other prefixes too, for example "F" is used for most plain data structures or non-UObject classes. 您也许已经注意到 C++ 中有些以 “A” 开头,而其他一些以 “U” 开头。前缀 “A” 代表它是一个 Actor 的子类,而前缀 “U” 代表它是一个 Object 的子类。还有其他一些前缀,比如 “F” 用于代表一个平铺的数据结构体,或者其他非 Uboject 的类。

    在虚幻 4 中编写游戏逻辑代码

    好了,现在开始稍微深入一些。我们将谈论一下创建游戏所需要的编程话题。因为您了解 Unity,我们来面向 C# 的用户解释 C++ 的功能,当然您也可以使用蓝图来完成几乎所有的事情!我们尽可能的为范例提供 C++ 的同时也提供蓝图。

    先说一下一些通用的游戏逻辑编程模式,以及如何在虚幻中实现。许多在 Unity 中的函数在虚幻中都有类似的函数。我们先从最常见的开始。

    Instantiating GameObject / Spawning Actor

    在 Unity 中,我们使用 Instantiate 函数来新建物体的实例。

    该函数使用任意的 UnityEngine.Object 类型(GameObject,MonoBehaviour 等),并创建它的拷贝。

    public GameObject EnemyPrefab;
    public Vector3 SpawnPosition;
    public Quaternion SpawnRotation;
    
    void Start()
    {
        GameObject NewGO = (GameObject)Instantiate(EnemyPrefab, SpawnPosition, SpawnRotation);
        NewGO.name = "MyNewGameObject";
    }

    在虚幻 4 中,根据不同的需要,有一些不同的函数用于创建物体。NewObject 用于创建新的 UObject 类型实例,而 SpawnActor 用于创建新的 AActor 类型实例。

    首先我们总体说一下 UObject 和 NewObject。在虚幻中 UObject 的子类很像 Unity 中 ScriptableObject 的子类。对于游戏过程中,它们是那些不需要在游戏世界中创建并看见的存在。

    在 Unity 中,如果要创建自己的 ScriptableObject 子类,可能会像下面这样的初始化:

    MyScriptableObject NewSO = ScriptableObject.CreateInstance<MyScriptableObject>();

    在虚幻中,如果要创建 UObject 的继承类,是像下面这样的初始化:

    UMyObject* NewObj = NewObject<UMyObject>();

    那么 Actor 呢?Actor 的在世界(C++ 中的 UWorld)中生成是通过 SpawnActor 方法。如何获取 World 对象?有些 UObject 会提供一个 GetWorld 的方法,所有的 Actor 则都具有这个方法。

    您可能已经注意到,并没有传递一个 Actor,我们传递了一个 Actor 的 “class” 来作为生成 Actor 的参数。在我们的范例中,是一个 AMyEnemy 类的任意子类。

    但如果想要创建某个东西的“拷贝”,就像 Unity 的 Instantiate 函数那样,该怎么做呢?

    NewObject 和 SpawnActor 函数也能通过给一个 “模板” 对象来工作。虚幻引擎会创建该一个对象的拷贝,而不是从零创建一个新的对象。这将会拷贝该对象的所有属性(UPROPERTY)和组件。

    AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
    {
        UWorld* World = ExistingActor->GetWorld();
        FActorSpawnParameters SpawnParams;
        SpawnParams.Template = ExistingActor;
        World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
    }

    您也许想知道“从零开始创建”在这里具体是什么意思。每个对象类在创建时都有一个默认模板,包含了它的默认属性和组件。在创建时如果您并不像修改这些默认属性,并没有提供你自己的模板,虚幻将使用这些默认值来创建该对象。为了更好的说明这个,我们先来看一下 MonoBehaviour 的例子:

    public class MyComponent : MonoBehaviour
    {
        public int MyIntProp = 42;
        public SphereCollider MyCollisionComp = null;
    
        void Start()
        {
            // Create the collision component if we don't already have one
            if (MyCollisionComp == null)
            {
                MyCollisionComp = gameObject.AddComponent<SphereCollider>();
                MyCollisionComp.center = Vector3.zero;
                MyCollisionComp.radius = 20.0f;
            }
        }
    }

    在上面这个例子中,有一个 int 属性,默认是 42,并有一个 SphereCollider 组件默认半径是 20。

    在虚幻 4 中,利用对象的构造函数也能达到同样的效果。

    UCLASS()
    class AMyActor : public AActor
    {
        GENERATED_BODY()
    
        UPROPERTY()
        int32 MyIntProp;
    
        UPROPERTY()
        USphereComponent* MyCollisionComp;
    
        AMyActor()
        {
            MyIntProp = 42;
    
            MyCollisionComp = CreateDefaultSubobject<USphereComponent>(FName(TEXT("CollisionComponent"));
            MyCollisionComp->RelativeLocation = FVector::ZeroVector;
            MyCollisionComp->SphereRadius = 20.0f;
        }
    };

    在 AMyActor 的构造函数中,我们为这个类设置了属性的默认值。请注意 CreateDefaultSubobject 函数。我们可以用它来创建组件并赋予组件默认值。所有子对象都将使用这个函数作为默认模板来创建,也可以在子类或者蓝图中对它进行修改。

    类型转换

    在这个例子中,获取了一个已知的组件,并将它转换为一个特定类型并有条件的做一些事情。

    Unity C#:

    Collider collider = gameObject.GetComponent<Collider>;
    SphereCollider sphereCollider = collider as SphereCollider;
    if (sphereCollider != null)
    {
            // ...
    }

    虚幻 4 C++:

    UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
    USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
    if (SphereCollider != nullptr)
    {
            // ...
    }

    销毁 GameObject / Actor

    Unity C++ 蓝图
    Destroy(MyGameObject);
    MyActor->Destroy();

    image_23.png

    销毁 GameObject / Actor (1 秒延迟)

    Unity C++ 蓝图
    Destroy(MyGameObject, 1);
    MyActor->SetLifeSpan(1);

    点击查看大图。

    禁用 GameObjects / Actors

    Unity C++ Blueprint
    MyGameObject.SetActive(false);
    // Hides visible components
    MyActor->SetActorHiddenInGame(true);
    
    // Disables collision components
    MyActor->SetActorEnableCollision(false);
    
    // Stops the Actor from ticking
    MyActor->SetActorTickEnabled(false);

    点击查看大图。

    通过组件访问 GameObject / Actor

    Unity C++ 蓝图
    GameObject ParentGO = 
    MyComponent.gameObject; 
     AActor* ParentActor = 
     MyComponent->GetOwner();

    点击查看大图。

    通过 GameObject / Actor 访问组件

    Unity

    MyComponent MyComp = gameObject.GetComponent<MyComponent>();

    C++

    UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();

    蓝图

    image_33.png

    查找 GameObjects / Actors

    // Find GameObject by name
    GameObject MyGO = GameObject.Find("MyNamedGameObject");
    
    // Find Objects by type
    MyComponent[] Components = Object.FindObjectsOfType(typeof(MyComponent)) as MyComponent[];
    foreach (MyComponent Component in Components)
    {
            // ...
    }
    
    // Find GameObjects by tag
    GameObject[] GameObjects = GameObject.FindGameObjectsWithTag("MyTag");
    foreach (GameObject GO in GameObjects)
    {
            // ...
    }
    
    // Find Actor by name (also works on UObjects)
    AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
    
    // Find Actors by type (needs a UWorld object)
    for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
    {
            AMyActor* MyActor = *It;
            // ...
    }

    image alt text

    // Find UObjects by type
    for (TObjectIterator<UMyObject> It; It; ++it)
    {
        UMyObject* MyObject = *It;
        // ...
    }
    
    // Find Actors by tag (also works on ActorComponents, use TObjectIterator instead)
    for (TActorIterator<AActor> It(GetWorld()); It; ++It)
    {
        AActor* Actor = *It;
        if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
        {
            // ...
        }
    }

    image alt text

    为 GameObjects / Actors 添加标签

    MyGameObject.tag = "MyTag";
    
    // Actors can have multiple tags
    MyActor.Tags.AddUnique(TEXT("MyTag"));

    image alt text

    为 MonoBehaviours / ActorComponents 添加标签

    // This changes the tag on the GameObject it is attached to
    MyComponent.tag = "MyTag";
    
    // Components have their own array of tags
    MyComponent.ComponentTags.AddUnique(TEXT("MyTag"));

    比较 GameObjects / Actors 和 MonoBehaviours / ActorComponents 的标签

    if (MyGameObject.CompareTag("MyTag"))
    {
        // ...
    }
    
    // Checks the tag on the GameObject it is attached to
    if (MyComponent.CompareTag("MyTag"))
    {
        // ...
    }
    
    // Checks if an Actor has this tag
    if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
    {
        // ...
    }

    image alt text

    // Checks if an ActorComponent has this tag
    if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
    {
        // ...
    }

    image alt text

    物理:刚体 vs. 元组件

    在 Unity 中药给一个 GameObject 物理特性,需要给它一个刚体组件。在虚幻中,任何元组件(C++ 中为 UPrimitiveComponent)都可以是物理对象。一些通用的元组件,比如 ShapeComponent(胶囊形,球形,盒形),StaticMeshComponent,以及 SkeletalMeshComponent。

    和 Unity 不同,Unity 将碰撞体和可视物体分列到不同的组件中。而虚幻则将潜在的物理碰撞和潜在的可视效果组合到了 PrimitiveComponent 中。任何在世界中具有形状的物体,要么就是能够被渲染显示,要么就是能作物理交互,它们都继承于 PrimitiveComponent。

    层 vs 通道

    在 Unity 中,它们被称为“层(Layer)”。虚幻使用碰撞通道(Collision Channel)来描述,这是类似的概念。可以在 此处 读到更多。

    RayCast vs RayTrace

    Unity C#:

    GameObject FindGOCameraIsLookingAt()
    {
        Vector3 Start = Camera.main.transform.position;
        Vector3 Direction = Camera.main.transform.forward;
        float Distance = 100.0f;
        int LayerBitMask = 1 << LayerMask.NameToLayer("Pawn");
    
        RaycastHit Hit;
        bool bHit = Physics.Raycast(Start, Direction, out Hit, Distance, LayerBitMask);
    
        if (bHit)
        {
            return Hit.collider.gameObject;
        }
    
        return null;
    }

    虚幻 4 C++:

    APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
    {
        // You can use this to customize various properties about the trace
        FCollisionQueryParams Params;
        // Ignore the player's pawn
        Params.AddIgnoredActor(GetPawn());
    
        // The hit result gets populated by the line trace
        FHitResult Hit;
    
        // Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)
        FVector Start = PlayerCameraManager->GetCameraLocation();
        FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
        bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
    
        if (bHit)
        {
            // Hit.Actor contains a weak pointer to the Actor that the trace hit
            return Cast<APawn>(Hit.Actor.Get());
        }
    
        return nullptr;
    }

    虚幻 4 蓝图:

    点击查看大图。

    触发器

    Unity C#:

    public class MyComponent : MonoBehaviour
    {
        void Start()
        {
            collider.isTrigger = true;
        }
        void OnTriggerEnter(Collider Other)
        {
            // ...
        }
        void OnTriggerExit(Collider Other)
        {
            // ...
        }
    }

    虚幻 4 C++:

    UCLASS()
    class AMyActor : public AActor
    {
        GENERATED_BODY()
    
        // My trigger component
        UPROPERTY()
        UPrimitiveComponent* Trigger;
    
        AMyActor()
        {
            Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));
    
            // Both colliders need to have this set to true for events to fire
            Trigger.bGenerateOverlapEvents = true;
    
            // Set the collision mode for the collider
            // This mode will only enable the collider for raycasts, sweeps, and overlaps
            Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
        }
    
        void BeginPlay()
        {
            // Register to find out when an overlap occurs
            OnActorBeginOverlap.AddDynamic(this, &AMyActor::OnTriggerEnter);
            OnActorEndOverlap.AddDynamic(this, &AMyActor::OnTriggerExit);
    
            Super::BeginPlay();
        }
    
        void EndPlay(const EEndPlayReason::Type EndPlayReason)
        {
            OnActorBeginOverlap.RemoveDynamic(this, &AMyActor::OnTriggerEnter);
            OnActorEndOverlap.RemoveDynamic(this, &AMyActor::OnTriggerExit);
    
            Super:EndPlay(EndPlayReason);
        }
    
        UFUNCTION()
        void OnTriggerEnter(AActor* Other);
    
        UFUNCTION()
        void OnTriggerExit(AActor* Other);
    };

    虚幻 4 蓝图:

    image alt text

    还可以在 这里 读到更多关于设置碰撞的响应。

    刚体运动

    Unity C#:

    public class MyComponent : MonoBehaviour
    {
        void Start()
        {
            rigidbody.isKinimatic = true;
            rigidbody.velocity = transform.forward * 10.0f;
        }
    }

    在虚幻 4 中,碰撞组件和刚体组件是同一个组件,它的基类是 UPrimitiveComponent,它也有不同的子类(USphereComponent,UCapsuleComponent 等)来配合不同的需求。

    虚幻 4 C++:

    UCLASS()
    class AMyActor : public AActor
    {
        GENERATED_BODY()
    
        UPROPERTY()
        UPrimitiveComponent* PhysicalComp;
    
        AMyActor()
        {
            PhysicalComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionAndPhysics"));
            PhysicalComp->SetSimulatePhysics(false);
            PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);
        }
    };

    输入事件

    Unity C#:

    public class MyPlayerController : MonoBehaviour
    {
        void Update()
        {
            if (Input.GetButtonDown("Fire"))
            {
                // ...
            }
            float Horiz = Input.GetAxis("Horizontal");
            float Vert = Input.GetAxis("Vertical");
            // ...
        }
    }

    虚幻 4 C++:

    UCLASS()
    class AMyPlayerController : public APlayerController
    {
        GENERATED_BODY()
    
        void SetupInputComponent()
        {
            Super::SetupInputComponent();
    
            InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent);
            InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent);
            InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent);
        }
    
        void HandleFireInputEvent();
        void HandleHorizontalAxisInputEvent(float Value);
        void HandleVerticalAxisInputEvent(float Value);
    };

    虚幻 4 蓝图:

    image alt text

    这里是在项目设置中输入属性的设置界面:

    image alt text

    关于如何设置输入可以从在 这里 阅读。

    常见问题

    如何自动加载最后一个项目?

    如果您习惯于 Unity 会自动加载上次工作的最后一个项目,在虚幻 4 中也一样可以做到。要开启这个功能的话,当打开项目时勾选“启动时总是加载最后一个项目”的选项。可以在任意时刻通过编辑器的主菜单的 编辑/编辑器首选项/加载和保存/启动 的部分来修改。

    哪里可以设置游戏的输入绑定?

    在 Unity 中,如果您习惯于为项目用 Input Manager 来设置默认的输入的话,虚幻 4 也一样。您可以打开项目设置,并选择 Input 的分类。然后就可以添加不同的案件(Action)或者摇杆控制(Axe)。给每一种控制一个名称和默认绑定。然后,在游戏中的 Pawn 中当该输入事件发生时就能获取回调函数。查看输入文档页面 来了解详情。

    如何修改项目的初始场景?

    可以在项目设置的分页中为项目设定初始场景。从主菜单中选择 编辑/项目设置->地图和模式 便能进行修改。

    如何运行游戏?

    运行游戏最简单的方式是点击编辑器的主工具栏上的“运行”按钮,这将会直接在编辑器窗口中运行游戏。如果想要作为单独的程序运行,点击“运行”按钮边上的下拉箭头,并选择“Standalone Game”。如果想要在移动设备或者网页浏览器中运行游戏,那需要使用工具栏中的“启动”按钮(相应的平台需要预先安装所需软件)。

    单位是什么?

    在 Unity 中,主要的测量单位是米,在虚幻 4 中,主要的测量单位是厘米。

    因此在 Unity 中移动一个单位(一米)相当于在虚幻 4 中移动 100 个单位(厘米)。

    如果想要在 Unity 中移动 2 英尺,那么相当于 0.61 个单位,而在虚幻 4 中则是 61 个单位。

    坐标系是怎么回事?那个方向朝上?

    Unity 和虚幻 4 都使用左手坐标系,但坐标轴则需要轮换一下。在虚幻 4 中,X 的正方向是“前”,Y 的正方向是“右”,Z 的正方向是“上”。

    如何查看游戏的输出日志?

    在虚幻 4 编辑器中,可以从“窗口->开发人员工具”的菜单中打开“输出日志”。也可以在运行游戏时增加 “-log” 命令行参数,在游戏窗口以外启动一个专用的日志窗口,这非常有用!

    说道日志输出,Debug.log 在哪里?

    在虚幻 4 中做日志高度的可定制化。阅读 这里 了解如何记录信息。

    如何抛出异常?

    在 Unity 中,您可能习惯于当发生问题是抛出异常。虚幻 4 则并不处理异常。取而代之的做法是,使用 “check()” 函数来触发严重的错误。可以传入一个错误信息提示。如果只是想报告一个错误,但并不希望打断整个程序,可以使用 “ensure()”。这将会记录一个完整堆栈的日志信息,但程序会继续执行。如果当前附加了调试器,那么这两个函数都会暂定并进入调试器。

    .NET Framework 去哪里了?

    和 Unity 不同,虚幻 4 并不使用 .NET Framework。虚幻 4 自有一套容器类和库。常见的容器来对照:

    .Net Framework 虚幻 4
    String FString,FText
    List TArray
    Dictionary TMap
    HashSet TSet

    这里 可以阅读到更多虚幻 4 的容器。

    代码改变时虚幻会自动重新加载吗?

    是的!您可以在编写代码是保持编辑器开启的状态。要在编写完成后直接从 Visual Studio 中编译代码,编辑器则会“热加载”您刚刚做的改动。也可以点击编辑器主工具栏上的编译 按钮。当您在 Visual Studio 调试器连接的状态下这也很有用。

    然后呢?

    感谢阅读本篇指引!这篇文章的创建时为了帮助各地的虚幻社区和虚幻开发者,我们很希望能收到您的反馈意见或者纠错意见。在我们不断了解如何能帮助大家过渡到虚幻 4 的过程中,我们将会保持改进这篇文档。

    我们还有很多虚幻 4 的学习资料!

    展开全文
  • 基于UE4的运行机制,当前地图包含的所有资源都会在进入该地图时候统一全部加载,这就导致了如果我们在这个地图中只使用到了一部分DataAsset中的数据,但是依然会把其中所有的数据全部加载进来,模型,贴图,材质等,...
  • LevelStream 实现超大无缝地图--官方文档学习 The Level Streaming feature makes it possible to load and unload map files into memory as well as toggle their visibility all during play....
  • 时间:2017年9月12日Esri CityEngine是将城市模型和其他3D GIS内容引入虚拟现实(VR)的好工具。它允许您为手机(见ArcGIS 360 VR)和台式PC创建VR体验。在这篇博文中,我们将逐步描述如何快速将3D数据导入桌面VR...
  • 今年是第二次参加UnrealOpenDay活动,收获颇丰,也认识了很多业界大咖。这里稍微详细点的总结一下会议内容和参会心得。首先是行程:会议9点签到,9...
  • 这次的版本带来了数百个虚幻引擎 4 的更新,包括来自 GitHub 的社区成员们提交的 145 个改进!感谢所有为虚幻引擎 4 添砖加瓦贡献的人们: alk3ovation, Allegorithmic (Allegorithmic), Alwin Tom (alwintom), ...
  • UnrealText:合成来自虚幻世界的真实场景文本图像 Abstract Synthetic data has been a critical tool for training scene text detection and recognition models. On the one hand,synthetic word images have ...
  • if (FCoreDelegates::OnMountPak.IsBound()) { 2. FCoreDelegates::OnMountPak.Execute(PakPath, 4); //Number should be 0-4; specifies search order ...https://answers.unrealengine.com/questions...
  • 自由骑士笃志原创- -欢迎转载,呃 BigWorld:澳大利亚 优点: 1:动态负载均衡,服务器承受能力好。  2:服务器有较高的容错性,对服务器状况有专业的记录和管理报表分析。  3:功能全面,使用非常方便,开发速度...
  • UnrealScript语言参考

    2011-01-23 01:15:00
    最初作者是Tim Sweeney (EpicGames)  内容UnrealScript语言参考介绍快速链接本文档的目的UnrealScript的设计目标虚幻引擎3中UnrealScript的新功能代码结构示例Unreal虚拟机对象层次类变量变量类型内置类型集合...
  • 这是第157篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间...模型中多个材质的渲染顺序 SpriteRenderer的屏幕填充实现 iOS如何导出Log Shader加载异常 Unity 2018....
  • 几款引擎比较 BigWorld Unreal CryEngine等
  • Wwise Unreal Engine 集成代码浅析 (一) 在Engine\Plugins\Wwise\Source下为Wwise在Unreal Engine内插件实现的相关代码,AkAudio文件夹下为运行时相关代码,AudiokineticTools下为编辑器工具相关...
  • 虚幻引擎3(Unreal Engine 3)概要虚幻引擎3概要 虚幻引擎3是一个面向下一代游戏机和DirectX 9个人电脑的完整的游戏开发平台,提供了游戏开发者需要的大量的核心技术、数据生成工具和基础支持。 虚幻引擎3的设计...
  • UE4 中的 C++ 编程介绍

    2019-01-24 11:12:03
    UE4 中的 C++ 编程介绍 ...https://docs.unrealengine.com/latest/CHN/Programming/Introduction/index.html ...Unreal Engine 4.9 虚幻 C++ 妙不可言! 此指南...
  • 主流的开源自动驾驶仿真框架,现在都是利用vc加UE4来构建,最近调研了一些仿真框架,重点研究了AirSim加UE4 以及Carla加UE4的解决方案,在建立编译环境及制作仿真环境和使用时,走了很多坑,在此进行一个简单的总结...
1 2 3 4 5 ... 8
收藏数 160
精华内容 64
热门标签
关键字:

unreal 加载 手动 模型