【UE4源代码观察】观察DDC(DerivedDataCache)

2023-11-15

【UE4源代码观察】观察DDC(DerivedDataCache)_YakSue的博客-CSDN博客

概念

DDC,全名DerivedDataCache(派生数据缓存)。很早就知道UE4里存在DDC这个概念,也发现了DDC占用了很多磁盘空间,也遇到过DDC导致了问题然后清理过之后问题消失的情况。但是DDC的细节从来没有深究过,好奇心驱使我想了解更多关于DDC的内容。

官方文档指出DDC的概念是:
●The Derived Data Cache (DDC) stores versions of Assets in the formats used by the Unreal Engine on its target platforms, as opposed to the source formats artists create that are imported into the Editor and stored in .uasset files。DDC存储了一个资源的版本,这个版本是UE在目标平台上所用的格式。与此相对的是Artist所创建的原始格式的资源,那些资源被导入到UE4编辑器中存储成了.uasset文件。
●Content stored in the DDC is disposable in that it can always be regenerated at any time using the data stored in the .uasset file。存储在DDC中的内容可以随时丢弃,因为他们可以随时由.uasset文件重新生成。
●Storing these derived formats externally makes it possible to easily add or change the formats used by the engine without needing to modify the source asset file。在外部存储派生格式是为了可以随时添加或更改引擎所用的格式,而不需要修改原始资源文件(指.uasset文件)

官方文档先给出了概念,然后补充了特点,随后说出了这么做的理由。看似已经讲明白了DDC的含义,但我还有很多疑问:
DDC的概括下来就是特定平台对应格式的资源版本,那么这里的对应平台是指什么?是指Windows、Android这种操作系统平台?(恐怕没这么简单)。此外,如果DDC是和.uasset文件所对应的,那么DDC文件夹的目录层级应该和.uasset文件一致的,但实际并不一致,而且.uasset文件是不是和DDC文件一一对应这也并不确定。

为了离上面这些问题的答案更近一步,我决定观察DDC模块的源代码。这个模块在Developer分类中,打开这个源代码的模块后我惊喜的发现他所包含的文件并不多,更重要的是:DDC模块只依赖于Core模块。看来理解他的成本并没有之前想象中那么高。
这篇博客接下来会先清点DDC模块中的class,观察他们的继承关系;程序内的对象;和其他的类有怎样的联系;它有哪些值得一提的函数或是变量;等等。之后尝试找出一些关于DDC的问题的答案。
清点DDC模块里的class
IDerivedDataCacheModule

它是DDC的模块类,内容很少:

/**
 * Module for the DDC
 */
class IDerivedDataCacheModule : public IModuleInterface
{
public:
    /** Return the DDC interface **/
    virtual FDerivedDataCacheInterface& GetDDC() = 0;
};

    1
    2
    3
    4
    5
    6
    7
    8
    9

.cpp中内容也很少,基本上,他只是拥有一个FDerivedDataCache类型的指针(是一个单例),在启动模块时创建,并提供能获取这个单例指针的函数:

class FDerivedDataCacheModule : public IDerivedDataCacheModule
{
    /** Cached reference to DDC singleton, helpful to control singleton's lifetime. */
    FDerivedDataCache* DDC;

public:
    virtual FDerivedDataCacheInterface& GetDDC() override
    {
        return InternalSingleton();
    }
    virtual void StartupModule() override
    {
        // make sure DDC gets created early, previously it might have happened in ShutdownModule() (for PrintLeaks()) when it was already too late
        DDC = static_cast< FDerivedDataCache* >( &GetDDC() );
    }
    virtual void ShutdownModule() override
    {
        FDDCCleanup::Shutdown();
        if (DDC)
        {
            DDC->PrintLeaks();
        }
    }
    FDerivedDataCacheModule():    DDC(nullptr)
    {
    }
};
IMPLEMENT_MODULE( FDerivedDataCacheModule, DerivedDataCache);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

FDerivedDataCacheInterface

这是一个接口类,含有大量的纯虚函数。他没有基类:

/**
 * Interface for the derived data cache
 * This API is fully threadsafe (with the possible exception of the system interface: NotfiyBootComplete, etc).
**/
class FDerivedDataCacheInterface

    1
    2
    3
    4
    5

它有一个函数是GetSynchronous:

/**
* Synchronously checks the cache and if the item is present, it returns the cached results, otherwise tells the deriver to build the data and then updates the cache
 * @param    DataDeriver    plugin to produce cache key and in the event of a miss, return the data.
 * @param    bDataWasBuilt if non-null, set to true if the data returned had to be built instead of retrieved from the DDC. Used for stat tracking.
 * @return    true if the data was retrieved from the cache or the deriver built the data sucessfully. false can only occur if the plugin returns false.
**/
virtual bool GetSynchronous(class FDerivedDataPluginInterface* DataDeriver, TArray<uint8>& OutData, bool* bDataWasBuilt = nullptr) = 0;

    1
    2
    3
    4
    5
    6
    7

这个函数的名字以中文的语法习惯来看有些怪,实际上Synchronous是副词(Synchronously,同步地)。因此这个函数的意思是以同步地方式来获得(指获得DDC)。从注释可以明白:它的作用是以同步地方式(即程序会立即返回结果)获得DDC,他会先检查缓存中是否有这个项目,有的话便会将结果存到OutData里,否则重新构建数据并更新缓存。它的参数是一个FDerivedDataPluginInterface指针。
于此相对的是GetAsynchronous函数(异步的方式):

/**
 * Starts the async process of checking the cache and if the item is present, retrieving the cached results, otherwise telling the deriver to build the data and then updating the cache
 * If the plugin does not support threading, all of the above will be completed before the call returns.
 * @param    DataDeriver    plugin to produce cache key and in the event of a miss, return the data.
 * @return    a handle that can be used for PollAsynchronousCompletion, WaitAsynchronousCompletion and GetAsynchronousResults
**/
virtual uint32 GetAsynchronous(class FDerivedDataPluginInterface* DataDeriver) = 0;

    1
    2
    3
    4
    5
    6
    7

注意:同步版返回了是否成功得到DDC,而异步版返回的值是创建的任务的handle。

此外,还有参数是TCHAR* CacheKey版:

/**
* Synchronously checks the cache and if the item is present, it returns the cached results, otherwise it returns false
 * @param    CacheKey    Key to identify the data
 * @return    true if the data was retrieved from the cache
**/
virtual bool GetSynchronous(const TCHAR* CacheKey, TArray<uint8>& OutData) = 0;

    1
    2
    3
    4
    5
    6

FDerivedDataCacheInterface的实现是FDerivedDataCache:

/**
 * Implementation of the derived data cache
 * This API is fully threadsafe
**/
class FDerivedDataCache : public FDerivedDataCacheInterface

    1
    2
    3
    4
    5

而他也有子类:

/**
 * Implementation of the derived data cache, this layer implements rollups
**/
class FDerivedDataCacheWithRollups : public FDerivedDataCache

    1
    2
    3
    4

这两个类都是在.cpp文件中定义的,而不是在.h中。这说明他们完全是私有的,不需要在外部访问,外部访问的接口全在FDerivedDataCacheInterface中定义了。但是程序运行时,是只有一个FDerivedDataCache单例的,而二者如何选择的逻辑在DerivedDataCache.cpp中的InternalSingleton函数中可以看到:
在这里插入图片描述
可以看到它是根据命令行参数来DDCNoRollups选择的。
FDerivedDataBackend

这个类也是个抽象的类,有纯虚函数。它起一个接口作用。没有基类:

class FDerivedDataBackend
{
public:
    // Singleton to retrieve the GLOBAL backend
    // @return Reference to the global cache backend
    static FDerivedDataBackend& Get();

    // Singleton to retrieve the root cache
    // @return Reference to the global cache root
    virtual FDerivedDataBackendInterface& GetRoot() = 0;

    // System Interface, copied from FDerivedDataCacheInterface
    virtual void NotifyBootComplete() = 0;
    virtual void AddToAsyncCompletionCounter(int32 Addend) = 0;
    virtual void WaitForQuiescence(bool bShutdown = false) = 0;
    virtual void GetDirectories(TArray<FString>& OutResults) = 0;
    virtual bool GetUsingSharedDDC() const = 0;

    // Mounts a read-only pak file.
    // @param PakFilename Pak filename
    virtual FDerivedDataBackendInterface* MountPakFile(const TCHAR* PakFilename) = 0;

    // Unmounts a read-only pak file.
    // @param PakFilename Pak filename
    virtual bool UnmountPakFile(const TCHAR* PakFilename) = 0;

    virtual void GatherUsageStats(TMap<FString, FDerivedDataCacheUsageStats>& UsageStats) = 0;
};

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

FDerivedDataBackendGraph是它的实现:

/**
  * This class is used to create a singleton that represents the derived data cache hierarchy and all of the wrappers necessary
  * ideally this would be data driven and the backends would be plugins...
**/
class FDerivedDataBackendGraph : public FDerivedDataBackend

    1
    2
    3
    4
    5

它在程序中也是只有一个全局的单例

/**
* Singleton to retrieve the GLOBAL backend
 *
 * @return Reference to the global cache backend
 */
static FORCEINLINE FDerivedDataBackendGraph& Get()
{
    static FDerivedDataBackendGraph SingletonInstance;
    return SingletonInstance;
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

这个类是在.cpp文件中定义的,而不是在.h中。这说明外部不用关心他的细节,所有需要访问的接口已经全在FDerivedDataBackend中定义了
FDerivedDataBackendInterface

这个类没有父类:

/**
 * Interface for cache server backends.
 * The entire API should be callable from any thread (except the singleton can be assumed to be called at least once before concurrent access).
**/
class FDerivedDataBackendInterface

    1
    2
    3
    4
    5

它有一个函数是GetCachedData:

/**Synchronous retrieve of a cache item
 *
 * @param    CacheKey    Alphanumeric+underscore key of this cache item
 * @param    OutData        Buffer to receive the results, if any were found
 * @return                true if any data was found, and in this case OutData is non-empty*/
virtual bool GetCachedData(const TCHAR* CacheKey, TArray<uint8>& OutData)=0;

    1
    2
    3
    4
    5
    6

这个函数的参数是一个TCHAR* CacheKey,之后得到的数据会存储在OutData中。

FDerivedDataBackendInterface有如下子类:
●FDerivedDataBackendAsyncPutWrapper
●FDerivedDataBackendCorruptionWrapper
●FDerivedDataBackendVerifyWrapper
●FDerivedDataLimitKeyLengthWrapper
●FFileSystemDerivedDataBackend
●FHierarchicalDerivedDataBackend
●FMemoryDerivedDataBackend
●FPakFileDerivedDataBackend

在FDerivedDataBackendGraph中有一些FDerivedDataBackendInterface类型的指针:

/** Root of the graph */
FDerivedDataBackendInterface*                    RootCache;

/** References to all created backed interfaces */
TArray< FDerivedDataBackendInterface* > CreatedBackends;

/** Instances of backend interfaces which exist in only one copy */
FMemoryDerivedDataBackend*        BootCache;
FPakFileDerivedDataBackend*        WritePakCache;
FDerivedDataBackendInterface*    AsyncPutWrapper;
FDerivedDataBackendInterface*    KeyLengthWrapper;
FHierarchicalDerivedDataBackend* HierarchicalWrapper;
/** Support for multiple read only pak files. */
TArray<FPakFileDerivedDataBackend*>        ReadPakCache;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

FDerivedDataPluginInterface

这也是一个接口类,没有父类:

/** Interface for data deriving backends
 * This API will not be called concurrently, except that Build might be called on different instances if IsBuildThreadsafe.**/
class FDerivedDataPluginInterface

    1
    2
    3

它的所有接口如下:

/** Get the plugin name, this is used as the first part of the cache key
* @return    Name of the plugin**/
virtual const TCHAR* GetPluginName() const = 0;

/** Get the version of the plugin, this is used as part of the cache key. This is supposed to
* be a guid string ( ex. "69C8C8A6-A9F8-4EFC-875C-CFBB72E66486" )
* @return    Version string of the plugin**/
virtual const TCHAR* GetVersionString() const = 0;

/** Returns the largest and plugin specific part of the cache key. This must be a alphanumeric+underscore
* @return    Version number of the plugin, for licensees.**/
virtual FString GetPluginSpecificCacheKeySuffix() const = 0;

/** Indicates that this plugin is threadsafe. Note, the system itself will not call it concurrently if this false, however, then you are responsible for not calling the system itself concurrently.
* @return    true if this plugin is threadsafe**/
virtual bool IsBuildThreadsafe() const = 0;

/** Indicated that this plugin generates deterministic data. This is used for DDC verification */
virtual bool IsDeterministic() const { return false; }

/** Indicated that this plugin generates deterministic data. This is used for DDC verification */
virtual FString GetDebugContextString() const { return TEXT("Unknown Context"); }

/** Does the work of deriving the data.
* @param    OutData    Array of bytes to fill in with the result data
* @return    true if successful, in the event of failure the cache is not updated and failure is propagated to the original caller.**/
virtual bool Build(TArray<uint8>& OutData) = 0;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

其中最值得注意的应该是Build函数了,它是负责构建DDC数据的,结果会放在OutData里。
FDDCCleanup

FDDCCleanup 是一个 FRunnable:

/**
 * DDC Filesystem Cache cleanup thread.
 */
class DERIVEDDATACACHE_API FDDCCleanup : public FRunnable

    1
    2
    3
    4

我的DDC数据存放在了何处?

官方文档指出了,DDC的路径在DefaultEngine.ini配置文件中有指明,同时,“用Epic Games Launcher安装的引擎启动” 和 “从源代码编译并启动”这两种方式会有区别。这在源代码的观察中也证实了这一点。
FDerivedDataBackendGraph有一个成员变量:

/** List of directories used by the DDC */
TArray<FString> Directories;

    1
    2

Directories虽然是列表,但实际上代码中只有一处对他有增加元素的操作,而且在此的断点只触发了一次:
在这里插入图片描述
从堆栈可以看到它是在引擎初始化阶段创建FDerivedDataBackendGraph单例时得到路径的。
再往这个堆栈上面找,就可以看到在FDerivedDataBackendGraph的构造函数中有逻辑:
在这里插入图片描述
可以看到他根据FApp::IsEngineInstalled()(是否是用安装版的引擎),来决定是用InstalledDerivedDataBackendGraph还是DerivedDataBackendGraph。
在使用源代码编译的引擎时(现在的情况),使用DerivedDataBackendGraph中的配置:

[DerivedDataBackendGraph]
MinimumDaysToKeepFile=7
Root=(Type=KeyLength, Length=120, Inner=AsyncPut)
AsyncPut=(Type=AsyncPut, Inner=Hierarchy)
Hierarchy=(Type=Hierarchical, Inner=Boot, Inner=Pak, Inner=EnginePak, Inner=Local, Inner=Shared)
Boot=(Type=Boot, Filename="%GAMEDIR%DerivedDataCache/Boot.ddc", MaxCacheSize=512)
Local=(Type=FileSystem, ReadOnly=false, Clean=false, Flush=false, PurgeTransient=true, DeleteUnused=true, UnusedFileAge=34, FoldersToClean=-1, Path=%ENGINEDIR%DerivedDataCache, EnvPathOverride=UE-LocalDataCachePath, EditorOverrideSetting=LocalDerivedDataCache)
Shared=(Type=FileSystem, ReadOnly=false, Clean=false, Flush=false, DeleteUnused=true, UnusedFileAge=10, FoldersToClean=10, MaxFileChecksPerSec=1, Path=?EpicDDC, EnvPathOverride=UE-SharedDataCachePath, EditorOverrideSetting=SharedDerivedDataCache, CommandLineOverride=SharedDataCachePath)
AltShared=(Type=FileSystem, ReadOnly=true, Clean=false, Flush=false, DeleteUnused=true, UnusedFileAge=23, FoldersToClean=10, MaxFileChecksPerSec=1, Path=?EpicDDC2, EnvPathOverride=UE-SharedDataCachePath2)
Pak=(Type=ReadPak, Filename="%GAMEDIR%DerivedDataCache/DDC.ddp")
EnginePak=(Type=ReadPak, Filename=%ENGINEDIR%DerivedDataCache/DDC.ddp)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

在这之中,Local和Shared分别指本地DDC和共享DDC。我这里是本地,看其中的Local的Path:

Path=%ENGINEDIR%DerivedDataCache

    1

即Engine目录的DerivedDataCache文件夹。这符合刚才调试的情况。
对于InstalledDerivedDataBackendGraph,它的Local的Path:

Path=%ENGINEVERSIONAGNOSTICUSERDIR%DerivedDataCache

    1

ENGINEVERSIONAGNOSTICUSERDIR即(Engine Version Agnostic User Dir 引擎版本无关用户路径)观察发现是在C:\Users\admin\AppData\Local\UnrealEngine\Common,此外上级目录还有其他特定版本的对应文件夹:
在这里插入图片描述
DDC数据什么时候触发构建,什么时候使用?

根据FDerivedDataCacheInterface::GetAsynchronous的注释就可知道:DDC数据在获取的时候,如果发现还不存在,就会触发构建。因此应该重点观察这个函数。
这个函数有同步、异步版本,还有FDerivedDataPluginInterface* DataDeriver和TCHAR* CacheKey参数版本,总共 2X2=4个版本。但他们内容很相似,都是会创建一个FAsyncTask<FBuildAsyncWorker>,不同的是,同步版本会立马执行,而异步版本会在其他线程中执行。而对于参数:
FDerivedDataPluginInterface* DataDeriver版本会使用这个对象:

FAsyncTask<FBuildAsyncWorker> PendingTask(DataDeriver, *CacheKey, true);

    1

TCHAR* CacheKey版本则不会使用:

FAsyncTask<FBuildAsyncWorker> PendingTask((FDerivedDataPluginInterface*)NULL, CacheKey, true);

    1

而对于他何时被调用,我本想总结出一些规律,但后来发现DDC在太多地方被用到了,这似乎是一个通用的方法,而每种资源都不一样。
已有的观察到的堆栈有:

ShaderMap相关:
在这里插入图片描述
UBodySetup这种UObject相关:
在这里插入图片描述
Texture相关:
在这里插入图片描述
Texture相关:
在这里插入图片描述
我知道还有很多地方调用。但我想最好的观察方法应该是研究某一种资源时,研究它的DDC如何获取的。
CacheKey的算法?

对于DDC来说,我想CacheKey的观察很重要,因为那些对CacheKey的生成有贡献的内容,一定就是一个资源派生数据时所基于的“平台”与“格式”。

对于这个问题,其实每种资源也有不同的答案,大体上分两类:
1.对于使用FDerivedDataPluginInterface的:

/**
* Internal function to build a cache key out of the plugin name, versions and plugin specific info
 * @param    DataDeriver    plugin to produce the elements of the cache key.
 * @return                Assembled cache key
**/
static FString BuildCacheKey(FDerivedDataPluginInterface* DataDeriver)
{
    FString Result = FDerivedDataCacheInterface::BuildCacheKey(DataDeriver->GetPluginName(), DataDeriver->GetVersionString(), *DataDeriver->GetPluginSpecificCacheKeySuffix());
    return Result;
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

具体就要看实现FDerivedDataPluginInterface的类的内部了。目前发现他有6个实现:
FDerivedDataAnimationCompression
FChaosDerivedDataCooker
FDerivedDataPhysXCooker
FDerivedAudioDataCompressor
FDerivedDataGeometryCollectionCooker
FDerivedDataNavCollisionCooker
2.对于不使用FDerivedDataPluginInterface的:

那么这个CacheKey的计算就更个性化了。
比如对于Texture:

/**
 * Constructs a derived data key from the key suffix.
 * @param KeySuffix - The key suffix.
 * @param OutKey - The full derived data key.
 */
static void GetTextureDerivedDataKeyFromSuffix(const FString& KeySuffix, FString& OutKey)
{
    OutKey = FDerivedDataCacheInterface::BuildCacheKey(
        TEXT("TEXTURE"),
        TEXTURE_DERIVEDDATA_VER,
        *KeySuffix
        );
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

而KeySuffix则由GetTextureDerivedDataKeySuffix函数得到:

/**
 * Computes the derived data key suffix for a texture with the specified compression settings.
 * @param Texture - The texture for which to compute the derived data key.
 * @param BuildSettings - Build settings for which to compute the derived data key.
 * @param OutKeySuffix - The derived data key suffix.
 */
 void GetTextureDerivedDataKeySuffix(const UTexture& Texture, const FTextureBuildSettings* BuildSettingsPerLayer, FString& OutKeySuffix)

    1
    2
    3
    4
    5
    6
    7

其中一段:

// build the key, but don't use include the version if it's 0 to be backwards compatible
OutKeySuffix = FString::Printf(TEXT("%s_%s%s%s_%02u_%s"),
    *BuildSettings.TextureFormatName.GetPlainNameString(),
    Version == 0 ? TEXT("") : *FString::Printf(TEXT("%d_"), Version),
    *Texture.Source.GetIdString(),
    *CompositeTextureStr,
    (uint32)NUM_INLINE_DERIVED_MIPS,
    (TextureFormat == NULL) ? TEXT("") : *TextureFormat->GetDerivedDataKeyString(Texture)
    );

    1
    2
    3
    4
    5
    6
    7
    8
    9

可以看到其中用到了TextureFormat(类型是ITextureFormat)

/**
 * Interface for texture compression modules.
 */
class ITextureFormat

    1
    2
    3
    4

构建是采用什么算法?

对于这个问题,答案依旧是根据不同资源有不同的算法。
对于每一种资源,可以搜索FDerivedDataCacheInterface关键字:
在这里插入图片描述
然后顺藤摸瓜找到算法。

例如,对于Texture,就可以观察到在FTexturePlatformData::Cache函数中,FTextureCacheDerivedDataWorker被创建随后工作,然后DDC的具体算法就在FTextureCacheDerivedDataWorker::DoWork()中。
总结

其实关于DDC还有很多疑问,但是目前明白的一个很重要的事情是:他和具体的资源关系密切。DDC模块虽然内容不多,但其实际上实现的是一个框架,而具体的DDC的键值与内容的逻辑,则在不同资源类型的代码中。
我想,这篇博客中虽然我没能彻底明白DDC,但它一定会在未来我研究一些资源的DDC时提供帮助。
————————————————
版权声明:本文为CSDN博主「YakSue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013412391/article/details/105546408/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【UE4源代码观察】观察DDC(DerivedDataCache) 的相关文章

  • 虚幻C++ http请求

    直接上代码 Fill out your copyright notice in the Description page of Project Settings pragma once include CoreMinimal h inclu
  • UE4和C++ 开发-新手常用C++API

    C 暴露给蓝图可编辑 UCLASS Blueprintable 创建FString FString Printf TEXT aa bb 蓝图调用变量 UCLASS ClassGroup Custom meta BlueprintSpawna
  • UE4中实现鼠标拖动游戏中的物体

    一 显示鼠标光标 启用鼠标点击事件 可以在关卡蓝图中或者游戏模式中加入下面代码 二 点击物体进入选中状态 三 根据通道获取命中结果 通过这个函数可以获取当前鼠标光标下接触的actor 重新设定actor位置即可 这里可以将类型转换去掉 就可
  • UE4 材质 生成随机数

    参考之前的文章中关于随机数和noize的 https papalqi cn 201707 cid 558 html
  • 【UE4】【C++】PlayerController、AIController获取玩家对应的Pawn

    先创建一个基本的C 类 Tank 因为要对Tank进行各种操作 移动 寻找目标 所以选择了Pawn类型 PlayerController 再创建一个C 类 TankPlayerController 用以控制玩家操作的对象 Tank 创建好后
  • 如何解决x盘莫名出现的msdia80.dll文件

    如何解决x盘莫名出现的msdia80 dll文件 参考文章 https zhuanlan zhihu com p 138954717
  • (UE4 4.20 )UE4的GC(垃圾回收)编程规范

    UObject系统的GC UPROPERTY 引用 当我们在一个UObject类声明各种继承UObject的 变量时 得加UPROPERTY 这个可以让UE4帮我们自动管理UObject的垃圾回收 UPROPERTY不仅仅用于反射变量到编辑
  • 想用好虚幻4引擎做游戏,你需要避免这些扰人的坑(备忘)

    在手游品质越发上扬的如今 已经有不少厂商开始使用一些性能更好的引擎 去尝试游戏制作了 而虚幻4引擎 以下简称UE4 就是其中之一 在这款引擎中已经诞生了诸如 铁拳7 地狱之刃 帕拉贡 等一系列大作 对玩家而言 这些作品都是不折不扣的视觉盛宴
  • UE4 DDC共享

    本人用的是源码引擎编译的 内网使用DDC 先创建一个共享文件夹 这个文件夹来保存共享资源 修改引擎的baseengine ini cpp DerivedDataBackendGraph Shared Type FileSystem Read
  • UE4 UE4 C++ Gameplay Abilities的GameplayCue

    UE4 UE4 C Gameplay Abilities的GameplayCue GAS参考文档 用GameplayCue 做一个玩家加血 buff效果 初始化 加血 加buff buff消失 加血的播放一个粒子特效 这个是用Gamepla
  • UE 材质学习

    值材质三原素 材质 材料 肌理 纹络 or 纹理 图案 Material Texture Pattern UE5中对应材质的 三原素 的内容 材质 Metallic 金属感 Roughness 粗糙度 Specular 高光 镜面 肌理 N
  • 2022年11月计划(cesium for ue源码抄写+ socket视频学习+ue4 tf视频学习)

    根据规划 进行cesium for ue源码抄写 网络视频教程 ue4 tf1视频学习
  • ue4中Pak文件中挂载的资源名称获取

    在pak文件中 会挂接很多资源 api也在变化 废话不多说 上代码 FPakPlatformFile GetPakPlatformFile FPakPlatformFile pakPlatformFile nullptr auto plat
  • UE4 UI实现改键功能

    主要内容 本文主要讲解如何在UI中实现自定义按键的功能类似于游戏中的改键操作 用到的是UE4自带的第三人称案例 因为第三人称自带了小白人和几个按键绑定就不用再手动去设置 实现步骤 1 创建两个UMG用来展示UI效果 1 创建WBP Key
  • ue中的经纬高转xyz的问题

    在ue中 做了个地球仪 发现经纬度转地心坐标系老是出问题 后来发现 是转ue时 x y坐标要互换 也对 因为在cesium for unreal中还有一系列ecef转ue的相关函数 即下面的代码中 xy需要互换 在ue中才能正常使用 偏心率
  • 11月8日 改良射线,蓝图 UE4斯坦福 学习笔记

    修改射线类型 更改了昨天的射线类型 void USInteractionComponent PrimaryInteract 射线 FHitResult FHit 碰撞体 FCollisionObjectQueryParams ObjectQ
  • UE4 命令工具打包

    用cmd进入UE4引擎的目录 Engine Build BatchFiles找到RunUAT bat cmd中输入以下命令 RunUAT BuildCookRun project F VidaUpdater VidaUpdater upro
  • 2021年11月6日-11月12日(ogre抄写+ue4视频,本周35小时,共1035小时,剩8965小时。)

    这周还不错 不但完成了本周学习任务 还完成了本月学习任务 方法就是 拼命抄源码 抄到吐时就再看看Ue4视频教程 内外兼修 可以在未来的日子里这么进行 每天5小时学习 还是进入状态的 5 7 35小时 共1035小时 剩8965小时 另外 去
  • 第十七篇:Unity/UE4如何实现Cave空间(一)

    首先什么叫CAVE空间 CAVE是围绕着观察者具有多个图像画面的虚拟现实系统 多个投影面组成一个虚拟空间 理论上CAVE是基于计算机图形学把高分辨率的立体投影技术和三维计算机图形技术 音响技术 传感器技术等综合在一起 产生一个供多人使用的完
  • UE4 UE4 C++ Gameplay Abilities 的AttributeSet和GameplayEffect

    UE4 UE4 C Gameplay Abilities 的AttributeSet和GameplayEffect GAS参考文档 仅是个人理解 参考 AttributeSet是设置玩家属性的比如生命值 最大生命值 GameplayEffe

随机推荐

  • 国产ChatGpt、AI模型盘点

    个人中心 DownLoad 一 百度 文心一言 百度的文心一言是一款基于深度学习技术的自然语言生成模型 能够生成各种类型的文本 包括新闻 小说 诗歌等 它采用了Transformer模型和GPT 2模型 能够生成高质量的文本 并且速度非常快
  • 2022-1-12 java运算符的学习记录

    2022 1 12 java运算符的学习记录 算数运算符 在java中有i 和 i俩种操作 前一种是先使用变量再自增 后一种是先自增再使用变量 因为java是强运算符号 所以不同的类型的变量加减 最终会趋向于高等级的类型的运算类型 是取整符
  • vggNet网络学习(网络架构及代码搭建)

    原论文 翻译链接 VERY DEEP CONVOLUTIONAL NETWORKSFOR LARGE SCALE IMAGE RECOGNITION VGGnet论文翻译 附原文 机器学习我不学习的博客 CSDN博客 网络架构 vggnet
  • 巨人互动

    游戏出海是指将原本面向国内市场的游戏产品进行调整和优化 以适应海外市场的需求 并进行推广和销售 下面小编讲讲关于游戏出海对于游戏效果的影响的一些讨论点 1 市场扩大 通过游戏出海 可以将游戏产品的目标受众从国内扩展到全球范围内 从而获得更多
  • 前后端node设置art-template,以及express后端搭建

    前后端node设置art template 以及express后端搭建 首先全局安装express generator yarn add express generator g express e npm i yarn add cross
  • 第十二章 Spring Cloud Config 统一配置中心详解

    目录 一 配置问题分析及解决方案 1 问题分析 2 解决方案 二 Spring Cloud Config 介绍 1 Spring Cloud Config特性 2 Spring Cloud Config作用 3 Spring Cloud C
  • 希尔排序(ShellSort)

    最后分析的基于比较的排序 之所以放在前面几个排序算法之后主要是因为虽然希尔排序很容易编写却很难分析 尤其是它的时间复杂度 希尔排序思想的提出是有原因的 在那个排序还基本都是2次型 插入 选择 冒泡 的年代 当人们经常使用 插入排序时发现有时
  • Kafka实战——简单易懂的生产者消费者demo

    单线程版本适合本地调试 多线程版本适合做压测 1 引入maven依赖
  • 泊松分布的矩母函数与特征函数

    矩母函数与特征函数 矩 母 函 数 与 特 征 函 数
  • 【企业了解】人人都是产品经理、鸟哥笔记、CSDN、稀土掘金(2020年11月稀土掘金被字节跳动,金融与科技)

    企业了解 人人都是产品经理 鸟哥笔记 CSDN 稀土掘金 前言 今天早上看 今日热榜官网 的时候 被一篇文章吸引 中国成功学迭代史 内容挺有意思的 然后发现这篇文章来自一个网站 人人都是产品经理 和我上次写 企业分析 鸟哥笔记 一样 我因为
  • Hive三种不同的数据导出的方式

    Hive三种不同的数据导出的方式 1 导出到本地文件系统 insert overwrite local directory home anjianbing soft export data app order city d row form
  • 2021-09-22

    linux防火墙查看状态 操作防火墙的命令 查看防火墙状态 systemctl status firewalld 让防火墙可用 systemctl enable firewalld 让防火墙不可用 systemctl disable fir
  • 信号——产生、处理、捕捉、接收、阻塞

    一个信号是一条小消息 它通知系统进程中发生了一个某种类型的事件 提供了一种处理异步事件的方法 每一种信号都有一个名字 在头文件
  • 用Matlab作函数的图像

    函数简介 1 作图函数是plot 其调用格式如下 plot y plot x y plot x y LineSpec plot x1 y1 s1 x2 y2 s2 x3 y3 s3 说明 1 plot y 绘出以向量y为纵坐标 y的个元素的
  • IPV6基本报头

    version 版本号 值为6 与ipv4作用相同 4bit Traffic class 流分类 相当于ipv4的TOS字段 用于qos 表示报文的类或者优先级 8bit Flow label 流标签 用于区分实时流量 标签 源地址可以确定
  • 使用vue-amap实现地图经纬度显示、具体地址显示、卫星图、路网路况、在鼠标按下的地方添加标注点和添加多个标注点

    文章目录 写在开头 一 本文目的 二 版本信息 三 在App vue中调用其他 vue文件 四 点击地图显示经纬度和具体地址 五 添加卫星图和路网路况 六 在鼠标按下的地方添加标注点 七 在地图上显示多个标注点 写在最后 写在开头 我的上篇
  • LeetCode每日一题之209长度最小的子数组

    文章目录 问题描述 方法一 暴力求解 方法二 滑动窗口 问题描述 方法一 暴力求解 暴力求解法 时间复杂度O n 2 空间复杂度O 1 暴力求解法的思想 每一次遍历数组 然后更新result的值 一个for循环作为起始位置 一个for循环作
  • 使用JavaScript实现MQTT客户端的创建

    随着物联网的快速发展 实现设备之间的可靠和高效通信变得至关重要 MQTT作为一种轻量级的 开放的消息传输协议 被广泛应用于物联网领域 本文将为您介绍如何使用MQTT实现物联网设备之间的通信 MQTT基本概念 MQTT是一种基于发布 订阅模型
  • Qt modbus slave 从站 封装好的类直接使用

    实现基本的功能 QT serialport serialbus modbusSlove h ifndef MODBUSSLOVE H define MODBUSSLOVE H include
  • 【UE4源代码观察】观察DDC(DerivedDataCache)

    UE4源代码观察 观察DDC DerivedDataCache YakSue的博客 CSDN博客 概念 DDC 全名DerivedDataCache 派生数据缓存 很早就知道UE4里存在DDC这个概念 也发现了DDC占用了很多磁盘空间 也遇