С++ & Unreal Engine
Статья пишется в первую очередь для себя и будет пополняться по мере изучения С++ и понимания принципов программирования. В ней допускаются погрешности и правки, но в то же время информация изложена предельно-доступным для новичка образом без «злой» терминологии и заходов на специфические знания.
Сперва, среда, в которой будем работать: не Visual Studio. Он медленный и спряжение с движком такое себе. Отличная альтернатива: JetBrains Rider.
На момент написания есть бета-версия, которая бесплатна и позволяет пользовать весь функционал без ограничений. Преимущества описывать смысла не имеет: он лучше буквально во всём.
Заметки.
.h
Header. В таких файлах формируется структура:
Constructor (Construction Script), переменные
Для классов Pawn там же лежат PlayerInput-ы, принимающие ввод от игрока
В header файле так же регистрируются функции, но сам функционал их прописывается в .cpp
.cpp
Там лежит вся логика, к примеру то, что исполняется во время Tick (каждый кадр), BeginPlay
Build/Run
После написания логики необходимо скомпилировать код. IDE проверит на ошибки и если всё отлично: скажет что компиляция успешно завершена. За это отвечают пара кнопок в верхней правой части экрана:
Быстрые обновления без перезагрузки движка
- Из Rider запускаем движок с помощью Run
- Удостоверяемся, что включен LiveCoding на стороне Unreal
Теперь, когда движок запущен и LiveCoding включен, можно нажимать лишь кнопку быстрой рекомпиляции из движка после внесения изменений в Rider. Так же, как если бы жмакали в блюпринтах Compile перед тестом.
https://unrealcommunity.wiki/logging-lgpidy6i
https://www.tomlooman.com/unreal-engine-ufunction-specifiers/
https://www.unrealengine.com/en-US/blog/c-for-blueprinters
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/IntroductionToCPP/
https://www.stevestreeting.com/2020/11/02/ue4-c---interfaces---hints-n-tips/
https://www.develop4fun.it/glossario/unreal-engine-fmathfinterpconstantto/
https://cpp.hotexamples.com/examples/-/UWorld/GetDeltaSeconds/cpp-uworld-getdeltaseconds-method-examples.html
https://nerivec.github.io/old-ue4-wiki/index.html#cat-Code
https://www.unrealengine.com/en-US/blog/ue4-libraries-you-should-know-about
Начинка классов.
Декларация (регистрация/объявление о существовании ) функции, у которой на выходе нет никаких переменных, но внутри которой происходит какая-то логика, происходит посредством использования конструкции:void название функции(тип переменной Значение);
Для быстрого создания функционального блока внутри .cpp файла, чтобы прописать начинку и функционал объявленной фукции в Rider используется функционал Alt+Space на выделенном наименовании. Либо через правую кнопку.
Generate Definition создаст необходимый блок внутри .cpp, куда можно будет записывать весь необходимый фукнкционал, а на входе будет ожидать переменную(значение) вида Float, которая здесь обозначена как AmountOfFantasticness.
Символ точки с запятой в конце завершает логический блок, очень внимательно к этому!
Декларация переменных, их типа, их свойств происходит так же в .h классе.
Компоненты начинаются с заглавной U (UStaticMeshComponent например) в видеUPROPERTY(ОткудаВидим, РедактируетсялиВБлюпринтах, Категория)
ТипКомпонента* Название;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player")
UStaticMeshComponent* MeshComp;
По-умолчанию те компоненты объекта, что будут задекларированы/объявлены в .h (хедер) файле, будут в памяти, будут иметь ссылки, но не будут видимы. Поэтому, чтобы отобразить компонент, помимо регистрации в хедер файле необходимо инициализировать компонент в .cpp файле, чтобы он стал видим и операбелен.
Назначение параметров внутри компонента.
->
служит для назначения одного из параметров внутри компонента.
Достаточно вписать тот же SpringArmComp и поставить стрелку, как Rider тут же предложит варианты:
Как добавить новую функцию?
Сначала нужно задекларировать наличие функции в .h файле
Допустим, это будет функция для включения фонарика:
Затем, добавить тело функции в .cpp файл, в которое занесём весь нужный нам функционал. Для теста я добавил здесь обычный PrintString, который выведет тестовую строчку текста на экран. Добавляю тело для фукнции, которая вызывается "единоразово" в секцию BeginPlay():
Важная заметка:
Есть разница междуvoid ASuperPawn::ToggleFlashLight()
и междуvoid ToggleFlashLight()
Заключается в том, что без указания класса, в котором функция создана, она будет абстрактной и в моем случае отображала ошибку. В дополнение к этому, используя горячие клавиши (бинды) я без указанияASuperPawn::
не смог вызывать эту фукцию, она была "невидима" для кода.
После, добавлю нужные бинды в настройках проекта, в секции Input:
После - подвязываю бинд на нужную функцию, указывая ссылку на то, что эта функция лежит в текущем классе:
Как добавить интерфейс
Необходимо первым делом создать класс интерфейса в С++ из представленных шаблонов (делается из движка, как и другие классы), затем, в .h файл интерфейса добавить нужные связки. Все связки делаются типом UFunction(BlueprintCallable, BlueprintImplementableEvent, Category="YourAmazingInterface")
После чего, этот интерфейс будет виден из движка:
Вызов интерфейса (к примеру, через LineTrace, происходит так:
Как сделать LineTraceByChannel
Как вывести сообщение на экран (Print String)
GEngine->AddOnScreenDebugMessage(-1, 2.5f, FColor::White, "Amazing Message!");
Как конвертировать значения в FString
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/StringHandling/
Как создать и добавить виджет на экран
в .h файл добавляем ссылки на класс виджета и на сам объект виджета:
(в моем случае название виджета - PauseMenu)
После, в .cpp файле выполняем следующий код создания и добавления:
Позже, мне захотелось передавать значение в функцию, когда я нажимаю кнопку, но просто так этого нельзя сделать, используя примерно вот такой шаблон:PlayerInputComponent->BindAction("InteractMouse", IE_Pressed, this, &ABuilderPawn::BeginInteract);
Поэтому, для того, чтобы можно было по кнопке вызывать функцию и передавать сразу в неё значение переменной, то конструкция выглядит так:
в .h добавляем помимо обозначения типа входящей переменной еще и шаблон на её использование в конкретной функции:
в .cpp я изменил эту функцию, скорректировав ввод в нее параметра boolean:
И бинд клавиш так же стал выглядеть иначе, используя теперь шаблон, передавая в функцию нужный параметр:
Draw Debug Box
DrawDebugBox(GetWorld(), CollisionBoxOrigin, CollisionHalfSize,
FColor::Yellow, true, 2, 0, 0.5);
Как подцепить элемент из Widget Blueprint
Используем мета-тег! Имя объекта и класс должны совпадать в блюпринте, чтобы связка заработала, иначе возможны вылеты.
UPROPERTY (meta = (BindWidget)) class UTextBlock* InGameMessage;
Допустим. Есть два объекта - родительский и дочерний. Мы лучом попали в какой-то объект. Пусть это будет AActor класс (почти-любой объект в игровом мире). Но мы хотим сделать каст по дочернему классу от него. Ссылка на объект имеется, допустим вида
AActor* ObjectOfInterest = HitResult.GetActor();
Из конструкции видно, что ObjectOfInterest имеет класс AActor (пока что).
Теперь, допустим нам надо сделать каст в другой класс, который является дочерним от AActor*, к примеру APowerfulHeadset*. Решаем так:
class APowerfulHeadset* Headset = Cast<APowerfulHeadset>(ObjectOfInterest);
Такая конструкция сразу сделает каст и в случае успеха -
назначит переменную Headset
После чего, из этой переменной, как из класса, можно будет доставать разные переменные.
Как доставать переменные из класса
Продолжая предыдущий абзац, представим, что мы добавили несколько переменных в наш класс Headset - (записываем и объявляем в .h):
// Номер играющего трека UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MainInfo") int32 SongNum; // Уровень громкости UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MainInfo") float Volume;
Достаём эти переменные вот таким образом:
float CurrentVolume = Headset->Volume; int32 CurrentTrack = Headset->SongNum;
Полезные классы и функции
UGameplayStatics::
UKismetSystemLibrary::