Введение в C ++ Программирование на UE4 -1 часть

By | November 11, 2015

Данный перевод является вольным переводом страницы по документации Unreal 4.Я не гарантирую что он на 100% подойдёт или будет корректным. В первую очередь я перевожу для себя с целью изучить данный движек, который я считаю лучшим среди существующих на текущий момент времени.

Спасибо за понимание.

Начало

Руководство по написанию кода на С++ на движке Unreal Engine является увлекательным занятием, и не волнуйтесь если у вас что-то не будет получаться. Программирование на Unreal достаточно не сложно, и не надо волноваться если у вас что-то не получается. Мы думаем о Unreal C++ как о помощнике в С++, который предоставляет большие возможности чтобы сделать проще С++ для всех.

Прежде чем продолжить изучение , важно что-бы для вас был знаком язык программирования С++ или другой подобный язык. Мы предполагаем что вы уже знакомы с С++ и имеете определённый опыт программирования на С++ или подобных языках таких как : С#, Java, Java-script, а так же вам знакомо большинство аспектов программирования.
Если Вам не знаком не один язык программирования, то вы можете попробовать писать логику через Блюпринты, которые являются равноценным подспорьем по написанию логике на языке программирования. С помощью блюпринтов так же можно создавать полноценные игры, получая от этого удовольствие 😉
При написании кода на С++ в Унреале, после прочтения руководства вы станните более лучше понимать основы Унреал программирования моделей.
C++ и Blueprints(Блюпринты)
Unreal Engine предоставляет два способа создания игровых элементов, Это блюпринты(визуальный скриптинг), а так же написание кода на C++. Используя С++, программисты могут создать базовый функционал игровой системы используя который дизайнеры могут создать полноценный уровень игры. Работая над одной игрой программист работает в своём редакторе, тот же Visual Studio или Xcode, а дизайнер работает в блюпринт редакторе(Blueprint Editor)(От себя: что-то буду преднамеренно пропускать, потому-что сплошная вода, потеря времени ;))

С учётом на писаного ранее,давайте взглянем на рабочий процесс программиста при построении и создании бизнес логики для дизайнера. Сейчас мы постараемся создать класс который позже будет доработан с помощью Blueprints дизайнером или программистом. В этом классе который мы создадим, мы добавим некоторые свойства, которые в последствии может установить или изменить дизайнер. Так же мы будем получать значения этих свойств с помощью визуального редактора в Блюпринт или в коде программы

Class Wizard (Мастер создания классов)

На рисунке ниже, мы создадим новый класс(Actor) помощью визарда, а далее продожим его изменение с помощью Blueprints  редактора. Мастер класс предназначен для генерации классов в С++. На самом деле их сам визард можно и не использовать для создания классов(кому как удобнее).

image_1

Второй шаг после создания класса, нам необходимо задать имя нового класа(если не задать имя то оно сгенирируется автоматически). От себя могу заметить, папку для создания вашего класса необходимо указать в souce вашего проекта.

image_2

После того как будет сгенерирован класс, Unreal откроет среду разработки для дальнейшего редактирования класса. Более подробное расмотрение мастера генерации классов мы рассмотрим попозже.

Содержимое файла который сгенерировал класс

#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public: 
    // Sets default values for this actor's properties
    AMyActor();
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;
};

Мастер класс генерирует два метода BeginPlay() и Tick() в качестве перегружаемых.

метод BeginPlay() – событие которое позволяет узнать вошел ли в игру Actor. BeginPlay является методом в котором можно реализовать начальную логику геймплея при появлении Actor.

Метод Tick() – Событие которое вызывается каждый кадр и которое принимает как параметр количество затраченного времени с момента последнего кадра. В данном методе можно сделать любую повторяющуюся логику. Если данный метод не нужен, то лучше его удалить, чтобы не перезагружать ресурсы компьютеры при геймплее.

В коде ниже представлена реализация конструктора который вызывается всегда при вызове класса

AMyActor::AMyActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you do not need it.
    PrimaryActorTick.bCanEverTick = true;
}

Making a property show up in the editor (Создание отображаемых свойств и редактирование)

Мы создали класс, теперь давайте добавим свойства которые можно будет устанавливать через визуальный редактор Unreal Editor. Связывание своиств производится с помощью специального макроса UPROPERTY() . Для того чтобы разрешить редактирование своиств в визуальном редакторе, необходимо задать  UPROPERTY(EditAnywhere), что даст в последующим право видеть и редактировать своиства класса.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    int32 TotalDamage;

    ...
};

Этого достаточно, чтобы редактировать значение новой переменной. Есть так же другие способы для задания редактирования и контроля редактирования переменных. Один из способов, это передача доп. информации через макрос UPROPERTY(). Добавив переменную TotalDamage вы её увидете в дополнительных своиствах. Так же можно задать своиста категорий к чему будет относиться данная переменная. Задание категории так же можно произвести через передачу параметров в макрос.

UPROPERTY(EditAnywhere, Category="Damage")
int32 TotalDamage;

Если пользователь захочет отредактировать данную категорию, то он её увидит в разделе под заголовком Damage на ряду с другими своиствами. Это очень хороший способ группировки своиств на ряду с другими настройками.

Теперь давайте через своиства макроса зададим возможность редактирование наших переменных через Blueprints

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
int32 TotalDamage;

BlueprintReadWrite – это спец параметр, который даёт возможность читать и редактировать любые переменные заданные после UPROPERTY

Все свойства которые доступны для доступа и работы с блюпринтами через код можно почитать по ссылке, на английском языке

Давайте добавим несколько различных свойств для того чтобы понять как они будут отрабатываться и контролироваться дизайнером.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    int32 TotalDamage;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    float DamageTimeInSeconds;

    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Transient, Category="Damage")
    float DamagePerSecond;

    ...
};

Своиство DamageTimeInSeconds может модифицироваться дизайнером.

Свойство DamagePerSecond  является расчётным свойством и оно скрыто за счёт VisibleAnywhere, как изменяется это свойство мы рассмотрим дальше. Флаг Transient означает переменная является не постоянная(не константной).

image_3

Установка значений переменной по умолчанию

Установка значений переменных в конструкторе ведётся так же как и простой код в С++, в конструкторе класса. Ниже приведено два примера идентичных по функционалу.

Первый пример:

AMyActor::AMyActor()
{
    TotalDamage = 200;
    DamageTimeInSeconds = 1.f;
}

Второй пример:

AMyActor::AMyActor() :
    TotalDamage(200),
    DamageTimeInSeconds(1.f)
{
}

Вот как будут выглядеть наши переменные при отображении в Blueprint(е)

image_4

Горячая перезагрузка(Hot Reloading)

Движек Unreal позваляет компилировать проект в исходный код двумя способами без закрытия проекта, так называемая горячая перезагрузка.

1) Первый способ является простая компиляция проекта через visua studio или XCode. При чём так же будут подцеплены все dll библиотеки которые присутствуют в движке.

image_6

2) Второй способ можно реализовать нажав клавишу Complite в Unreal

image_7

Вы можете использовать любой из доступных способов.

Расширение классов C++ с помощью Blueprints

До сих пор мы создавали простые классы геймплея с помощью мастера класса С++, добавляя некоторые свойства для редактирования дизайнером. Давайте добавим те же классы со стороны дизайнера.

Для начала необходимо создать BluePrint класс из класса AMyActor. Стоит обратить внимание на название нового создаваемого класса. Имя нового класса буде MyActor, которое будет наследовать класс AMyActor. Это необходимо для того чтобы у дизайнера было понимание, какой класс мы наследуем, для дальнейшей работы.

image_8

После нажатия select, в Blueprint Создаст новый класс с Вашим заданным именем. Как видно на скрине классу дали имя CustomActor1

image_9

Это наш первый класс который имеет определённые свойства которые можно задать дизайнером. Первоначально установим свойства  TotalDamage на 300, а так же установим время доставки домага в 2 секунды, установив значение Damage Time in second. Должно получиться следующее

image_10

Те расчёты которые были сделаны программой не соответствуют ожидаемому результату. Должно было 150, а отображается 200. Причина ошибки является то, что наш урон был вычислен в первую секунду, а потом свойства были инициализированны в процессе перезагрузки метода.  Приведённый код ниже решает данную проблему неточного вычисления.

void AMyActor::PostInitProperties()
{
    Super::PostInitProperties();

    CalculateValues();
}

void AMyActor::CalculateValues()
{
    DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}

#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
    CalculateValues();

    Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif

Как можете заметить, метод PostEditChangeProperty() находится внутри инструкции

#if #ifdef

Не смог корректно перевести

This is so that building your game only the code that you need for the game, removing any extra code that might increase the size of your executable unnecessarily.

Теперь у нас есть код который соответствует ожидаемому результату и переменная DamagePerSecond отображает ожидаемое значение, что можно видеть ниже

image_11

Вызов функций через C++ и Blueprint

 

Во время создания геймплея в С++ коде, у дизайнеров должна быть возможность вызывать эти функции в Blueprint. Давайте посмотрим как может обращаться дизайнер к созданным классам через С++.

Вызов функции созданной через С++ достаточно проста и наглядно видно на рисунке ниже. Для того чтобы дизайнер мог обращаться к функциям прописанным в С++ из Blueprint, необходимо прописать функцию после макроса.

UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();

После добавления функции в Макрос UFUNCTION()  можно наблюдать где данная функция будет доступна в Blueprint. Чтобы отнести функцию к категории, необходимо добавить название категории в передаваемых параметрах макроса. Отношение категории к функции наглядно видно на картинке.

image_12

Как мы видим , функция отнесена к соответствующей категории “damage”. В коде Blueprint показано как задать изменение отображаемых свойство TotalDamage с последующим вызовом функции и пересчётом зависимых переменных.

image_13

Большая часть движка может быть изменена с помощью blueprint, из ранее написанного кода на C++ использую макрос UFUNCTION(). Но, тем не менее лучшим подходом является программирование логики через С++. В первую очередь это связано с оптимизацией кода и поведения игры.

Давайте расмотрим ещё один способ взаимодействия дизайнера и программиста. Предположим, что нам необходимо уведомлять дизайнера о предстоящем событии на которые должен реагировать код через blueprint.Часто это бывает некоторые эффекты или например восстановление актёра который ране был скрыт. Фрагмент кода ниже показывает как это можно реализовать.

UFUNCTION(BlueprintImplementableEvent, Category="Damage")
void CalledFromCpp();

Эта функция вызывается как и любая другая функция из C++, В движке Unreal генерируется база С++ которая реализует функцию вызова Blueprint VM.Если Blueprint заполняет тело текущего метода, то функция ведёт себя как обычный класс в С++

UFUNCTION(BlueprintNativeEvent, Category="Damage")
void CalledFromCpp();

По факту будет сгенерирован метод.

void AMyActor::CalledFromCpp_Implementation()
{
    // Do something cool here
}

Стоит упомянуть, что метод будет сгенерирован автоматически и в заголовок необходимо будет добавить декларацию _Implementation(). Это касается всех версий выше 4.8UE

 

ссылка на вторую часть : вторая часть

Category: Uncategorized