
Здравствуйте меня зовут Дмитрий. Я занимаюсь созданием компьютерных игр на Unreal Engine в качестве хобби. Сегодня расскажу как добавить поддержку dxf файлов в Unreal Engine. (Исходники как всегда в конце статьи).
DXF — это открытый формат векторной графики, разработанный компанией Autodesk. В силу своей открытости этот формат поддерживается огромным количеством редакторов векторной графики.
Итак начнем с создания класса который будет содержать информацию об импортированном файле.
UCLASS(BlueprintType) class DXFPLUGINRUNTIME_API UDXFSketch : public UObject { GENERATED_BODY() public: #if WITH_EDITORONLY_DATA UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings) class UAssetImportData* AssetImportData; virtual void PostInitProperties() override; #endif // WITH_EDITORONLY_DATA UPROPERTY(VisibleAnywhere, BlueprintReadOnly) TArray<FDXFLayer> Layers; UPROPERTY() float DXFBackGroundSize; };
Здесь надо заметить что мы добавили в класс объект UAssetImportData этот объект будет содержать информацию о файле исходнике, и нужен для реимпорта ассета. В методе PostInitProperties() создается экземпляр данного класса. Массив объектов FDXFLayer собственно и содержит в себе всю информацию из dxf файла.
Ассет создан теперь нужно для него создать factory класс. Подробней о создании нового ассета можно почитать в этой статье.
UCLASS() class UDXFSketchFactory : public UFactory, public FReimportHandler { GENERATED_UCLASS_BODY() // UFactory interface virtual UObject* FactoryCreateBinary(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const uint8*& Buffer, const uint8* BufferEnd, FFeedbackContext* Warn) override; virtual bool CanCreateNew() const override; // End of UFactory interface // Begin FReimportHandler interface virtual bool CanReimport(UObject* Obj, TArray<FString>& OutFilenames) override; virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths) override; virtual EReimportResult::Type Reimport(UObject* Obj) override; virtual int32 GetPriority() const override; // End FReimportHandler interface bool LoadFile(UDXFSketch* Sketch, const uint8*& Buffer, const uint8* BufferEnd); };
Основным отличием factory класса для импортированных ассетов от обычных, заключается в использовании метода FactoryCreateBinary вместо FactoryCreateNew. Этому методу кроме прочих параметров предается ссылка на массив байт (которые явлются импортированным файлом) и указатель на конец этого массива.
И конечно-же конструктор:
UDXFSketchFactory::UDXFSketchFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UDXFSketch::StaticClass(); Formats.Add(TEXT("dxf;DXF")); bCreateNew = false; bEditorImport = true; }
Чтобы файл можно было ре-импортировать необходимо также в базовые классы добавить класс FReimportHandler который добавит метод Reimport.
Назначение метода LoadFile понятно и так. Чтобы не изобретать велосипед для парсинга файла я использовал библиотеку dxflib от ribbonsoft.
Ассет теперь импортируется. Но чтобы понять что находится в импортированном файле не плохо бы создать редактор для ассета. (Подробней про создание редактора для ассета можно почитать здесь). Полностью пересказывать предыдущую статью я не стану просто скажу, что нам нужно создать производный объект от FAssetEditorToolkit в котором мы создадим необходимые для нас вкладки (В данном случае это вкладка вьюпорта, в котором мы будем отрисовывать данные, и вкладка панели свойств). Создание панели свойст уже было рассмотрено. Поэтому поговорим о вьюпорте.
Во вкладке вьюпорт мы создадим объект SDXFEditorViewport
class SDXFEditorViewport : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SDXFEditorViewport) { } SLATE_ARGUMENT(TWeakPtr<FDXFAssetEditor>, CustomEditor) SLATE_END_ARGS() public: void Construct( const FArguments& InArgs); TSharedPtr<FSceneViewport> GetViewport( ) const; TSharedPtr<SViewport> GetViewportWidget( ) const; TSharedPtr<SScrollBar> GetVerticalScrollBar( ) const; TSharedPtr<SScrollBar> GetHorizontalScrollBar( ) const; void UpdateScreen(); protected: TSharedRef<SWidget> GenerateViewOptionsMenu() const; private: // Callback for clicking the View Options menu button. FReply HandleViewOptionsMenuButtonClicked(); // Callback for the horizontal scroll bar. void HandleHorizontalScrollBarScrolled( float InScrollOffsetFraction ); // Callback for getting the visibility of the horizontal scroll bar. EVisibility HandleHorizontalScrollBarVisibility( ) const; // Callback for the vertical scroll bar. void HandleVerticalScrollBarScrolled( float InScrollOffsetFraction ); // Callback for getting the visibility of the horizontal scroll bar. EVisibility HandleVerticalScrollBarVisibility( ) const; // Callback for clicking an item in the 'Zoom' menu. void HandleZoomMenuEntryClicked( double ZoomValue ); // Callback for getting the zoom percentage text. FText HandleZoomPercentageText( ) const; // Callback for changes in the zoom slider. void HandleZoomSliderChanged( float NewValue ); // Callback for getting the zoom slider's value. float HandleZoomSliderValue( ) const; void HandleLayerActive(int Num); void HandleAllLayersActive(); private: // Pointer back to the Asset editor tool that owns us. TWeakPtr<FDXFAssetEditor> AssetEditor; // Level viewport client. TSharedPtr<class FDXFEditorViewportClient> ViewportClient; // Slate viewport for rendering and IO. TSharedPtr<FSceneViewport> Viewport; // Viewport widget. TSharedPtr<SViewport> ViewportWidget; // Vertical scrollbar. TSharedPtr<SScrollBar> TextureViewportVerticalScrollBar; // Horizontal scrollbar. TSharedPtr<SScrollBar> TextureViewportHorizontalScrollBar; // Holds the anchor for the view options menu. TSharedPtr<SMenuAnchor> ViewOptionsMenuAnchor; };
Это объект интерфейса он создает все элементы интерфейса (меню ползунки и т.д) кроме того он создает объект FDXFEditorViewportClient в котором собственно и будет происходить отрисовка примитивов загруженных из файла.
class FDXFEditorViewportClient: public FViewportClient { public: /** Constructor */ FDXFEditorViewportClient(TWeakPtr<FDXFAssetEditor> InTextureEditor, TWeakPtr<SDXFEditorViewport> InTextureEditorViewport); /** FViewportClient interface */ virtual void Draw(FViewport* Viewport, FCanvas* Canvas) override; virtual bool InputKey(FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed = 1.0f, bool bGamepad = false) override; virtual UWorld* GetWorld() const override { return nullptr; } /** Returns the ratio of the size of the Texture texture to the size of the viewport */ float GetViewportVerticalScrollBarRatio() const; float GetViewportHorizontalScrollBarRatio() const; void SetZoom(double ZoomValue); void ZoomIn(); void ZoomOut(); double GetZoom() const; DrawVar Vars; //variables for drawning viewport private: /** Updates the states of the scrollbars */ void UpdateScrollBars(); /** Returns the positions of the scrollbars relative to the Texture textures */ FVector2D GetViewportScrollBarPositions() const; private: /** Pointer back to the Texture editor tool that owns us */ TWeakPtr<FDXFAssetEditor> AssetEditor; /** Pointer back to the Texture viewport control that owns us */ TWeakPtr<SDXFEditorViewport> AssetEditorViewport; };
Собственно редактор создан но есть ещё одна мелочь. В Unreal Engine есть такое понятие как Thumbnail это такая маленькая картинка которая отображается в контент-браузере в место значка ассета. Чтобы создать этот Thumbnail нужно создать обект производный от UThumbnailRenderer.
UCLASS() class UDXFThumbnailRenderer : public UThumbnailRenderer { GENERATED_BODY() // Begin UThumbnailRenderer Object virtual void GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const override; virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget* Viewport, FCanvas* Canvas) override; // End UThumbnailRenderer Object };
В этом объекте имеется метод Draw который собственно и нарисует Thumbnail. Конечноже после создания этого объекта его нужно зарегистрировать.
void FDXFPluginEditor::StartupModule() { // Register DXFSketch AssetActions TSharedRef<IAssetTypeActions> Action = MakeShareable(new FDXFSketchAssetActions); IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get(); AssetTools.RegisterAssetTypeActions(Action); CreatedAssetTypeActions.Add(Action); //Registrate ToolBarCommand for costom graph FDXFToolBarCommandsCommands::Register(); //Registrate Thumbnail render UThumbnailManager::Get().RegisterCustomRenderer(UDXFSketch::StaticClass(), UDXFThumbnailRenderer::StaticClass()); }
Теперь откуда же запускать рендер Thumbnail? Я в качестве такова места выбрал метод HandleReimportManagerPostReimport обекта FDXFAssetEditor этот метод выполняется после импорта файла:
void FDXFAssetEditor::HandleReimportManagerPostReimport(UObject* InObject, bool bSuccess) { TArray<UObject*> SelectedObjects; SelectedObjects.Add(InObject); AssetData = CastChecked<UDXFSketch>(InObject); if (bSuccess) { PropertyEditor->SetObjects(SelectedObjects); } DXFViewport->UpdateScreen(); FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo(AssetData); if (RenderInfo != NULL) { RenderInfo->Renderer; //Render Thumbnail } }

Но как можно использовать импортированный ассет? К сожалению наложить dxf файл в качестве текстуры не получится. Но можно например загрузить точки и использовать их координаты для расстановки объектов. Или создать так называемую SplineMesh и вытянуть её вдоль какой-то линии. Пока что плагин распознает линии, замкнутые контуры и точки (которые получаются если в adobe illustrator кисточкой ткнуть в холст, эти точки представляют из себя сплайны состоящие из 10 точек).
Собственно на этом все. Проект я сделал в виде плагина поэтому чтобы добавить поддержку dxf в ваш проект достаточно создать в его директории папку Plugins и закинуть туда папку DXFPlugin, чтобы увидеть исходники плагина в VS нужно удалить старый файл VS- проекта и сгенерировать новый. (Подробней про плагины можно почитать здесь)
Проект с исходниками здесь
