Здравствуйте меня зовут Дмитрий. Я занимаюсь созданием компьютерных игр на Unreal Engine в качестве хобби. Моя игра имеет процедурно генерируемый мир, но для того чтобы создать более интересный уровень, я решил использовать заранее определенные участки уровня. Но возникает вопрос как задать участки где уровень будет генерироваться, а где нет. Для этого я создал собственный режим редактирования, под катом я опишу его создание, исходники в конце статьи.
Итак начнем с создания нового режима редактирования:
class FEdModeCustom : public FEdMode
{
public:
FEdModeCustom();
~FEdModeCustom();
virtual void Tick(FEditorViewportClient* ViewportClient,float DeltaTime) override;
// FEdMode interface
virtual bool UsesToolkits() const override;
void Enter() override;
void Exit() override;
virtual bool InputKey( FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent ) override;
virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy *HitProxy, const FViewportClick &Click ) override;
virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; //FEdMode: Render elements for the Draw tool
virtual bool IsSelectionAllowed(AActor* InActor, bool bInSelection) const override; //Check to see if an actor can be selected in this mode - no side effects
virtual void ActorSelectionChangeNotify() override; //Called when the currently selected actor has changed
virtual bool ShouldDrawWidget() const override;
virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override;
// End of FEdMode interface
//Render
void DrawPrevewGird(FPrimitiveDrawInterface* PDI);
void DrawBlankCells(FPrimitiveDrawInterface* PDI);
class ATestAct* EditorModel;
void ForceRealTimeViewports(const bool bEnable, const bool bStoreCurrentState);
TSharedPtr<class FCustomToolBase> GetActiveTool() const { return ActiveTool; }
void SetActiveTool(TSharedPtr<FCustomToolBase> ActiveTool);
void ApplyBrush(FEditorViewportClient* ViewportClient);
static FEditorModeID EM_EdModeCustom;
private:
UMaterial* OverlayMaterial;
void UpdateGridCursorPosition(const FSceneView* View, FViewport* Viewport);
ATestAct* FindTestActorOnScen();
TSharedPtr<class FCustomToolBase> ActiveTool;
FIntVector GridCursorPosition;
bool bToolActive;
class SCustomEdit* EditPanel;
};
Enter и Exit — вызываются при входе и выходе из режима редактирования. При входе в режим редактирования мы создаем элементы интерфейса режима, которые будут отображаться в его вкладке. Эти элементы описаны в файле SCustomEdit.h, я не стану его здесь приводить.
InputKey — вызывается при получении событий от устройств ввода, то есть нажали кнопку получили событие IE_Pressed отпустили, получили IE_Released.
Render — отрисовывает сцену.
IsSelectionAllowed — Определяет можно ли выделять объекты или нет.
ActorSelectionChangeNotify — вызывается если выделение все таки произошло
ShouldDrawWidget — Определяет нужно рисовать виджет на объекте или нет (Виджет это такая штука с тремя разноцветными стрелками которая появляется на выбранном объекте если вы его перемещаете.)
InputDelta — Не дает камере перемещаться если зажата одна из кнопок мыши.
Для большего удобства я создал два инструмента. Первый инструмент это Paint при помощи него мы выбираем ячейки на которых не надо генерировать уровень (Shift инвертирует инструмент). Второй инструмент это Select при помощи него можно расставлять элементы на выбранные ячейки. У каждого инструмента есть свои методы Render() и InputKey(), которые мы вызываем из соответствующих методов режима редактирования.
Но просто так новый режим редактирования не появится. Чтобы он появился его нужно зарегистрировать в методе StartupModule().
void FLevelDrawEditorModule::StartupModule()
{
FCustomCommands::Register();
FEditorModeRegistry::Get().RegisterMode<FEdModeCustom>
(
FEdModeCustom::EM_EdModeCustom,
FText::FromString("Level Draw"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.ViewOptions", "LevelEditor.ViewOptions.Small"),
true, 400
);
//Registrate detail pannel costamization for TestActor
FTestActDetailCustomization::RegestrateCostumization();
}
После этого у нас появится вот такое вот меню:
Теперь самое главное где-же хранить полученную информацию. Информация хранится в объекте TestAct. Его нужно разместить на сцене если на сцене разместить несколько TestAct, между ними можно переключатся кликая по их иконкам.
UCLASS()
class LEVELDRAW_API ATestAct : public AActor
{
GENERATED_BODY()
public:
ATestAct();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gird)
FVector GridCellSize;
UPROPERTY(Transient)
UModeSettings* ModeSettings;
UPROPERTY()
FLevelMap LevelMap;
private_subobject:
#if WITH_EDITORONLY_DATA
UPROPERTY()
UBillboardComponent* SpriteComponent;
#endif //WITH_EDITORONLY_DATA
};
Итак что мы имеем:
GridCellSize — Этот параметр определяет размер ячеек которые будут отображаться.
ModeSettings — Параметры этого объекта отображаются на панели свойств режима редактирования. В нем задаются две точки при помощи которых можно подсветить себе сетку, а также выводится имя конкретного TestAct и кнопка для стирания данных.
Вы можете спросить а почему я прикрепил этот объект к TestAct а не к самому режиму редактирования? Дело в том что режим редактирования «живет» только когда вы на него переключитесь. И если-бы я прикрепил этот объект к режиму редактирования. То настройки обнулялись каждый раз когда происходило переключение на другой режим.
Кроме того можно заметить что указатель на объект UModeSettings обозначен как UPROPERTY(Transient). Параметр Transient не дает редактору сохранять объект UModeSettings при нажатии кнопки сохранить. А вот для чего нужен UPROPERTY?
Важно: в Unreal Egine имеется сборщик мусора, который удаляет все объекты если не осталось ни одного указателя определенного как UPROPERTY, указывающего на этот объект. Также объект можно защитить используя флаг RF_RootSet.
YourObjectInstance->SetFlags(RF_RootSet);
Конечно все вышесказанное относится только к объектам наследникам UObject. Если объект не является наследником UObject сборщик мусора ему не грозит.
LevelMap — Собственно именно в этом объекте и сохраняется информация о ячейках. Этот объект состоит из четырех двумерных массивов по одному на каждую четверть плоскости. Вот здесь уже параметр UPROPERTY нужен для того чтобы при нажатии на кнопку сохранить карту, сохранялся объект FLevelMap.
SpriteComponent — Просто ссылка на иконку, которая будет отображаться на экране редактора если объект ATestAct разместить на карте.
Вот собственно и все для дальнейшего изучения рекомендую смотреть исходники.
Проект с исходным кодом здесь.
Автор: deema35