Skip to content

Instantly share code, notes, and snippets.

@derofim
Last active October 10, 2023 20:02
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save derofim/fa21005b04b01836cd92ec227d3fff57 to your computer and use it in GitHub Desktop.
Save derofim/fa21005b04b01836cd92ec227d3fff57 to your computer and use it in GitHub Desktop.
UE4 logging, Overlap & Hit events, playing sounds, Actor location, Particle System Parameter

Предполагается, что вы уже знакомы с C++, знаете что такое конструктор, битовые поля, отличаете обычный enum от enum types. Также как минимум слышали про то, как в Unreal Engine поменять карту нормалей у объекта и что такое Blueprint Class. Весьма желательно знание английского на уровне подходящем для прочтения официальной документации Unreal Engine.

Данная серия уроков будет акцентировать внимание на C++ в Unreal Engine.

  1. Создаем новый C++ класс MyActor на базе Actor ( см. https://docs.unrealengine.com/latest/INT/Programming/QuickStart/1/index.html )

Вы также можете создать файлы исходного кода вручную сохранив их в папке имя_вашего_проекта\Source\имя_вашего_проекта радом с файлом имя_вашего_проекта.Build.cs.

Будет создан класс с следующей структурой:

UCLASS()
class [PROJECTNAME]_API ALightSwitchCodeOnly : public AActor
{
GENERATED_BODY()

};

Часть кода генерируется Unreal Engine, файлы вида *.generated.h должны подключаться последними. Макросу GENERATED_BODY() следует находиться в начале класса:

#pragma once

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

UCLASS()
class SCP_API 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;

	UPROPERTY(EditAnyWhere)
		UShapeComponent* Root;
};

Конструктор AMyActor() вызывается при создании класса и может использоваться для задания начальных значений.
Функция BeginPlay() вызывается при начале функционирования объекта, например, при создании в игре. Допустим, в игре есть очередь на создание машин. Конструктор AMyActor() машин уже вызвался при добавлении в очередь создания, а машина еще не создана BeginPlay() в игре т.к. на заводе выключилось электропитание.
Функция Tick( float DeltaSeconds ) вызывается при обновлении объекта. Параметр DeltaSeconds хранит время прошедшее с последнего обновления и может использоваться для выполнения действий вне зависимости от числа кадров в секунду (fps).

Макросы UCLASS(), UPROPERTY() и UFUNCTION() предоставляют информацию игровому редактору.
Например UPROPERTY может позволить редактировать переменную в свойствах объекта внутри редактора:

/** point light component */
UPROPERTY(VisibleAnywhere, Category = "Switch Components")
class UPointLightComponent* PointLight1;

Это называется UNREAL PROPERTY SYSTEM (REFLECTION), подробнее на https://www.unrealengine.com/blog/unreal-property-system-reflection

  • VisibleAnywhere - поле видно, но не редактируемо
  • EditAnywhere - Поле редактируемо
  • BlueprintReadOnly - Может считываться из Blueprint
  • BlueprintReadWrite - Может редатироваться из Blueprint
  • BlueprintCallable - Может вызываться из Blueprint
  • SaveGame - Сериализуется для сохранения игры
  • Category - Категория отображаемая в окне свойств, поддерживаем подкатегории "Category|SubCategory".
  • DisplayName - отображаемое имя. По умолчанию это имя переменной разделенное пробелами ('MyVariableName' -> 'My Variable Name').

Подробнее на https://wiki.unrealengine.com/UPROPERTY и https://wiki.unrealengine.com/Blueprints,_Creating_Variables_in_C%2B%2B_For_Use_In_BP

Поддержку UNREAL PROPERTY SYSTEM в visual studio можно обеспечить UE4 Intellisense https://marketplace.visualstudio.com/items?itemName=RxCompiLe.UE4Intellisense

Заметьте, что движком используется сборщик мусора https://wiki.unrealengine.com/Garbage_Collection_Overview
Если на UPROPERTY объект где-то существует указатель в UPROPERTY, то он не будет удален (иначе уничтожается). Объекты без UPROPERTY не затрагиваются сборщиком мусора.

  1. Перетаскиваем MyActor из папки "C++ Classes" в Content Browser в редактор мира (Level Editor). Код MyActor доступен ниже.
  2. Сохраняем все, делаем построение (Build), Компилируем код (Можно из Visual Studio или из Unreal Editor). Если какие-то модельки/звуковые файлы отсутствуют, то замените их пути на свои.
  3. Запускаем игру. MyActor должен представлять из себя шар (если не найдена 3д модель FirstPersonProjectileMesh, можете заменить на свою) перемещающийся в пространстве (если speed установлен > 0). При столкновении игрока и шара игрок должен подлетать и проигрываться звук OnOverlapSound (Можете заменить звук Collapse01 на свой). При попадании пули в MyActor должен проигрываться звук OnHitSound. Убедитесь что включена обработка столкновений.

UWorld

Уровень загружаемый по умолчанию можно указать в Edit > Project Settings > Maps & Modes. Каждый уровень имеет класс настроек мира [WorldSettings], поля которого можно изменять в редакторе. Используя эти поля создается класс UWorld, который создает объект GameMode в сцене. Объекты GameMode используются для создания объектов PlayerController, менеджеров и т.д..

Секция Default Modes позволяет установить класс GameMode по умолчанию, которым будут пользоваться все уровни которым явно не назначено иное. Стартовая точка игры - конструктор класса GameMode (или его наследника).

Ось Z в UE4 направлена вверх.

  • X - вперед, назад
  • Y - влево, вправо
  • Z - вверх, вниз UE4 дает осям вращения наименования: Roll, Pitch и Yaw.

Roll, Pitch и Yaw

Используются методы класса UWorld для операции с объектами на сцене (find, spawn, destroy). Можно получить экземпляр UWorld используя функцию GetWorld() класса PlayerController.

Загрузка сцены: GetWorld()->ServerTravel(string URL);

URL – путь к сцене, который может содержать дополнительные параметры. Например: "/Game/Maps/<map_name>?<key_1>=<value_1>&<key_2>=<value_2>"

Чтение параметров в коде: (GameMode class) GetIntOption(OptionsString, <key_1>, <default_value>);

Actors, Components, Objects

  • Object - основной "строительный блок". Почти всё наследуется от UObject. Поддержимет сборку мусора (garbage collection), предоставление метаданных (UProperty) в редактор (Unreal Editor), сериализацию (serialization for loading and saving). Поиск объектов по типу:
// Find UObjects by type
for (TObjectIterator<UMyObject> It; It; ++it)
{
    UMyObject* MyObject = *It;
    // ...
}
  • Actors - объекты, которые можно поместить на уровень. Хранят позицию, поворот, масштаб. Разновидностями AActor также являются StaticMeshActor, CameraActor и PlayerStartActor. Иерархию классов можно посмотреть в документации
    Процесс создания нового экземпляра Actor называется - спавн [spawn]. Спавн объекта Actor производится функцией UWorld::SpawnActor(). Эта функция создает новый экземпляр класса и возвращает указатель на созданного Actor. UWorld::SpawnActor() может использоваться только для спавна классов, унаследованных от Actor.
    Например, можно клонировать Actor:
AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
    UWorld* World = ExistingActor->GetWorld();
    FActorSpawnParameters SpawnParams;
    SpawnParams.Template = ExistingActor;
    World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}

Уничтожение объектов:

MyActor->Destroy(); // Мнгновенно уничтожает
MyActor->SetLifeSpan(1); // Уничтожает с задержкой в 1 секунду

Активация/дективация:

MyActor->SetActorHiddenInGame(true); // Hides visible components
MyActor->SetActorEnableCollision(false); // Disables collision components
MyActor->SetActorTickEnabled(false); // Stops the Actor from ticking

AActor может иметь теги:

// Actors can have multiple tags
MyActor.Tags.AddUnique(TEXT("MyTag"));

// Checks if an Actor has this tag
if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
{
    // ...
}

if (MyGameObject.CompareTag("MyTag"))
{
    // ...
}

Поиск AActor по названию, типу или тегу:

// Find Actor by name (also works on UObjects)
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
 
// Find Actors by type (needs a UWorld object)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
        AMyActor* MyActor = *It;
        // ...
}

// Find Actors by tag (also works on ActorComponents, use TObjectIterator instead)
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
    AActor* Actor = *It;
    if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
    {
        // ...
    }
}
  • Components - функциональные элементы добавляемые к Actors. Например, компонент Spot Light позволит Actor испускать свет, а Audio Component проигрывать звук. Компонент может иметь теги:
// Components have their own array of tags
MyComponent.ComponentTags.AddUnique(TEXT("MyTag"));
// Checks if an ActorComponent has this tag
if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{
    // ...
}
// Checks the tag on the GameObject it is attached to
if (MyComponent.CompareTag("MyTag"))
{
    // ...
}
  • Pawns - Игровые аватары (не обязательно человекоподобные) наследуемые от Actor. Управляются игроком или ИИ.
  • Character - Игровой персонаж (человекоподобный) наследуемый от Pawn Actor. Включает столкновения, настройки управления и т.д.
  • PlayerController - преобразует ввод игрока в игровые деяствия. Может управлять Pawn или Character.
  • AIController - управляет Pawn предствляя игровых персонажей (NPC).
  • GameMode - задает игровые правила.
  • GameStates - содержит информацию о состоянии игрового клиента (игровой счет, количество ботов и т.д.).
  • PlayerStates - содержит информацию о состоянии игрока или бота (ник, здоровье и т.д.).

Подробнее на:

Заметьте именование в виде AActor, это стандарт программирования https://docs.unrealengine.com/latest/INT/Programming/Development/CodingStandard/

  • Шаблонные классы имеют префикс T
  • Классы наследуемые от UObject имеют префикс U
  • Классы наследуемые от AActor имеют префикс A
  • Классы наследуемые от SWidget имеют префикс S
  • Абстрактные интерфейсы имеют префикс I https://wiki.unrealengine.com/Interfaces_in_C%2B%2B
  • Перечисления имеют префикс E
  • Логические переменные имеют префикс b ("bPendingDestruction", "bHasFadedIn").
  • Большинство остальных классов имеют префикс F

Существуют общие стандарты именования http://www.tomlooman.com/ue4-naming-convention/ :

  • Blueprint: BP_assetname_01
  • Blueprint Interface: BPI_InventoryItem_01
  • Material: M_Grass_01
  • Material Instance: MI_Grass_01
  • Static Mesh: SM_Wall_01
  • Skeletal Mesh: SK_Character_01
  • Texture: T_Grass_01_D
  • Particle System: P_Fire_01
  • Sound: S_HitImpact_Mono_01

Для текстур используются суффиксы, например:
Normal map - T_Grass_01_N

  • Diffuse/Color Map: _D
  • Normal Map: _N
  • Emissive Map: _E
  • Mask Map: _M
  • Roughness Map: _R
  • Metallic Map: _MT
  • Specular: _S
  • Displacement: _DP
  • Ambient Occlusion: _AO
  • Height Map: _H
  • Flow Map: _F
  • Light Map (custom): _L

См. подробнее на https://github.com/Allar/ue4-style-guide

Также используются специальные типы:

  • TCHAR - символ
  • uint8 - unsigned bytes (1 byte)
  • int8 - signed bytes (1 byte)
  • uint16 - unsigned "shorts" (2 bytes).
  • int16 - signed "shorts" (2 bytes).
  • uint32 - unsigned ints (4 bytes).
  • int32 - signed ints (4 bytes).
  • uint64 - unsigned "quad words" (8 bytes).
  • int64 - signed "quad words" (8 bytes).
  • PTRINT - integer that may hold a pointer

Комментарии пишутся в стиле JavaDoc:

        /**
         * Calculate a delta-taste value for the tea given the volume and temperature of water used to steep.
         * @param VolumeOfWater - Amount of water used to brew in mL
         * @param TemperatureOfWater - Water temperature in Kelvins
         * @param OutNewPotency - Tea's potency after steeping starts, from 0.97 to 1.04
         * @return the change in intensity of the tea in tea taste units (TTU) per minute
         */

Используется собственная стандартная библиотека https://docs.unrealengine.com/latest/INT/API/index.html
Пример:

TMap<FString, int32> MyMap;
for (TPair<FString, int32>& Kvp : MyMap)
    {
        UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), Kvp.Key, *Kvp.Value);
    }

Полезные классы:

#include "Misc/Crc.h"
FString myPuzzleName = ...
const uint myPuzzleID = FCrc::StrCrc32(myPuzzleName);
 switch(myPuzzleID)
 {
     case validPuzzles[PuzzleID::1A1B]:
             // https://answers.unrealengine.com/questions/440987/strongly-typed-enums-and-switch-statements.html?sort=oldest
 }
#include "Kismet/KismetMathLibrary.h"
//...
 float a = 1.1; float b = 2.2; float c = 3.3;
 float d = FMath::Max3(a, b, c);
 UE_LOG(LogTemp, Warning, TEXT("The max is: %f"), d);

Подробнее на https://www.unrealengine.com/blog/ue4-libraries-you-should-know-about

Перечисления имеют тип:

    UENUM()
    enum class EThing : uint8
    {
        Thing1,
        Thing2
    };

Перечисления используемые как флаговые битовые маски определяют операции с помощью ENUM_CLASS_FLAGS:

    enum class EFlags
    {
        None  = 0x00,
        Flag1 = 0x01,
        Flag2 = 0x02,
        Flag3 = 0x04
    };

    ENUM_CLASS_FLAGS(EFlags)
    // ...
    if ((Flags & EFlags::Flag1) != EFlags::None) ...

UShapeComponent, UStaticMeshComponent

Компоненты определяют функционал Actor. Например, если Actor - машина, то у нее могут быть компоненты руль и педали.

Список возможных компонентов можно найти на https://docs.unrealengine.com/latest/INT/Engine/Components/index.html и https://docs.unrealengine.com/latest/INT/API/Runtime/Engine/Components/index.html

  • UStaticMeshComponent - трехмерная модель из полигонов.
  • UShapeComponent - используется для создания столкновений, срабатывания событий, задания положения, создания путей. Примером Shape Component является Sphere Component задающий сферическую форму.
  • UAudioComponent - управляет звуком. Позволяет добавить Sound Wave (аудио файл) или Sound Cue (составной настраиваемый звук) к Actor (например, звук горения к факелу). Имеют настройки схожие с Actor фонового звука.

В примере мы создаем компоненты:

  • Root (UBoxComponent) - главный компонент
  • MeshComponent (UStaticMeshComponent) - компонент для обработки столкновений и вывода 3д модельки
	UPROPERTY(EditAnyWhere)
		UShapeComponent* Root;

	UPROPERTY(EditAnyWhere)
		UStaticMeshComponent* MeshComponent;
	Root = CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));
	
	RootComponent = Root;

	Root->bGenerateOverlapEvents = true;
	Root->SetCollisionProfileName(TEXT("OverlapAll"));

Функция CreateDefaultSubobject<UBoxComponent>(TEXT("Root")) - создает компонент типа UBoxComponent.

	MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
	MeshComponent->SetupAttachment(RootComponent);
	MeshComponent->SetHiddenInGame(false);

Заметьте RootComponent = Root;. RootComponent - главный компонент объекта, мы его устанавливаем в созданный UBoxComponent. RootComponent хранит действительную позицию объекта и позиция остальных компонентов задается относительно RootComponent.

SetupAttachment или AttachTo позволяет добавить один компонент к другому (так, чтобы один за другим следовал). SetupAttachment является предпочтительным перед AttachTo.

Можно наследоваться от UActorComponent для создания своих компонентов.

Пример создания компонента:

TSubobjectPtr<USceneComponent> SceneComponent = PCIP.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneComp"));

RootComponent = SceneComponent;

Можно получить AActor зная его компонент:

AActor* ParentActor = MyComponent->GetOwner();

Или наоборот, получить компонент зная его AActor:

UMyComponent* MyComp = Cast<UMyComponent>(MyActor->GetComponentByClass(UMyComponent::StaticClass()));

FObjectFinder

В примере мы находим 3д модельку сферы и сохраняем указатель на нее в переменную VisualAsset:
Пример поиска меша (можно скопировать путь до нужного меша в Content Browser нажав Copy Refference, нужна только часть между кавычками).

	static ConstructorHelpers::FObjectFinder<UStaticMesh> VisualAsset(
		TEXT("/Game/FirstPerson/Meshes/FirstPersonProjectileMesh.FirstPersonProjectileMesh")
	);

Проверить найден ли VisualAsset и задать 3д модельку компонента можно кодом:

	if (VisualAsset.Succeeded())
	{
		MeshComponent->SetStaticMesh(VisualAsset.Object); // 3д моделька
		MeshComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f)); // позиция относительно основы Root
		MeshComponent->SetWorldScale3D(FVector(1.0f)); // масштаб
	}

UShapeComponent, UStaticMeshComponent

Можно добавить компоненты через Add Component в Details Panel ( Свойствах обьекта, см. https://docs.unrealengine.com/latest/INT/Programming/QuickStart/4/index.html ), но можно и в коде.
Определим компонент:

	UPROPERTY(EditAnyWhere)
		UStaticMeshComponent* MeshComponent;

Зададим статичный меш по умолчанию используя FObjectFinder и SetStaticMesh:

	static ConstructorHelpers::FObjectFinder<UStaticMesh> VisualAsset(
		TEXT("/Game/FirstPerson/Meshes/FirstPersonProjectileMesh.FirstPersonProjectileMesh")
	);

	if (VisualAsset.Succeeded())
	{
		MeshComponent->SetStaticMesh(VisualAsset.Object);
		MeshComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
		MeshComponent->SetWorldScale3D(FVector(1.0f));
	}

Также при необходимости можно получить список компонентов определенного Actor нужного класса, например, класса UStaticMeshComponent:

 TArray<UStaticMeshComponent*> Comps;
 
 GetComponents(Comps);
 if(Comps.Num() > 0)
 {
     UStaticMeshComponent* FoundComp = Comps[0];
        //do stuff with FoundComp
 }

Или преобразовать тип полученного GetComponentByClass компонента:

UPrimitiveComponent *  Primitive  =  MyActor -> GetComponentByClass ( UPrimitiveComponent :: StaticClass ()); 
USphereComponent *  SphereCollider  =  Cast < USphereComponent >( Primitive ); 
if  ( SphereCollider  !=  nullptr ) 
{ 
        // ... 
}

Tick, bCanEverTick, SetActorLocation

Функция Tick вызывается каждый кадр.

Например, используя Tick можно изменять положение с течением времени.
Добавим переменную в MyActor.h:

float RunningTime = 0;

Изменим фунцию Tick в MyActor.cpp:

// Called every frame
void AMyActor::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

	FVector NewLocation = GetActorLocation();
	float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime)) * speedScale;
	NewLocation.Y += DeltaHeight * 20.0f;       //Scale our height by a factor of 20
	RunningTime += DeltaTime;
	SetActorLocation(NewLocation);
}

Заметьте, что скорость можно регулировать в редакторе (благодаря UPROPERTY(EditAnyWhere)) изменяя переменную speedScale.

	UPROPERTY(EditAnyWhere)
		float speedScale = 10.0f;

В конструкторе можно задать bCanEverTick. Если bCanEverTick = false, то функция Tick не будет вызываться.

 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

Также существуют функции:

  • TickActor - функция вызывающая Tick
  • ExecuteTick - функция вызывающая TickActor
  • ReceiveTick - вызывается Tick, зависит от blueprint
  • TickComponent - Фунция tick для отдельного компонента. примеры можно посмотреть тут, тут и тут. Не забудьте установить bCanEverTick в true.

Логирование

Вывести текст на экран можно функцией AddOnScreenDebugMessage:

if (GEngine) GEngine->AddOnScreenDebugMessage(-1, 1.5, FColor::White,TEXT("Text here"))

Вывести текст для отладки в консоль (Window -> Developer Tools -> Output Log) можно функцией UE_LOG:

UE_LOG(LogTemp, Warning, TEXT("Your message"));

Примеры:

GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("This is an on screen message!"));
UE_LOG(LogTemp, Log, TEXT("Log text %f"), 0.1f);
UE_LOG(LogTemp, Warning, TEXT("Log warning"));
UE_LOG(LogTemp, Error, TEXT("Log error"));
FError::Throwf(TEXT("Log error"));
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Dialog message")));

Подробнее про логирование можно прочитать на https://wiki.unrealengine.com/Logs,_Printing_Messages_To_Yourself_During_Runtime#Related_Tutorial

Collision, SetCollisionProfileName

SetCollisionProfileName(TEXT("OverlapAll")); говорит, что объект будет просчитывать столкновения.

"OverlapAll" - это Collision Preset, полный список на https://docs.unrealengine.com/latest/INT/Engine/Physics/Collision/Reference/

О различных типах столкновений подробнее написато на https://docs.unrealengine.com/latest/INT/Engine/Physics/Collision/ и https://www.unrealengine.com/blog/collision-filtering

OnOverlap, OnHit

OnComponentBeginOverlap

OnComponentBeginOverlap срабатывает когда actors перекрывают друг друга, разрешены Overlaps, включено Generate Overlap Events. Например, когда игрок зашел в trigger (перекрывающее столкновение - игрок внутри зоны).

When receiving an overlap from another object's movement, the directions of 'Hit.Normal' and 'Hit.ImpactNormal' will be adjusted to indicate force from the other object against this object.

void AMyActor::OnOverlap(class UPrimitiveComponent* OverlappingComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
  • OverlappingComp - The Component on the executing Actor that was overlapped
  • OtherActor - The other Actor involved in the collision.
  • OtherComp - The component on the other Actor involved in the collision that was overlapped.
  • OtherBodyIndex - index of the body that was collided
  • bFromSweep - Whether collision is from sweep
  • SweepResult - Sweep Result, useful for bouncing.

Желательно делать проверку на существование обьекта (!= nullptr) и столкновения с самим собой (!= this):

void ALightSwitchCodeOnly::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)  
{  
    // Other Actor is the actor that triggered the event. Check that is not ourself.  
    if ( (OtherActor != nullptr ) && (OtherActor != this) && ( OtherComp != nullptr ) )  
    {  
        // Turn off the light  
        PointLight->SetVisibility(false);  
    }  
}  

Для события OnComponentBeginOverlap возможно понадобится включить в настройках объекта "Generate Overlap Events" или установить в коде для компонента bGenerateOverlapEvents = true.

OnComponentHit:

Предназначен для блокирующих столкновений, например, для события удара о твердую стену.

Для события OnComponentHit возможно понадобится включить в настройках объекта "Simulation Generate Hit Events" или установить в коде для компонента SetNotifyRigidBodyCollision(true).

If you are creating movement using Sweeps, you will get this event even if you don't have the flag "Simulation Generate Hit Events" selected. This occurs as long as the Sweep stops you from moving past the blocking object.

NormalImpulse will be filled in for physics-simulating bodies, but will be zero for swept-component blocking collisions.

void AMyActor::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
  • HitComponent - The Component on the executing Actor that was hit.
  • OtherActor - The other Actor involved in the collision.
  • OtherComponent - The component on the other Actor involved in the collision that was hit.
  • NormalImpulse - The force that the Actors collided with.
  • Hit - All the data collected in a Hit, you can pull off and "break" this result to gain access to the individual bits of data.

Если события не срабатывают проверьте настройки столкновений, пропробуйте установить "Use Default Collision".

см. подробнее:

AddDynamic

В примере мы используем OnOverlap и OnHit для проверки событий столкноения.

	MeshComponent->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnOverlap);
	MeshComponent->OnComponentHit.AddDynamic(this, &AMyActor::OnHit);

См. подробнее https://wiki.unrealengine.com/Event_handling

Упражнение: RemoveDynamic

После назначения делегата его следует убрать вызвав RemoveDynamic в функции EndPlay, например:

    void BeginPlay()
    {
        // Register to find out when an overlap occurs
        OnActorBeginOverlap.AddDynamic(this, &AMyActor::OnTriggerEnter);
        OnActorEndOverlap.AddDynamic(this, &AMyActor::OnTriggerExit);
 
        Super::BeginPlay();
    }
 
    void EndPlay(const EEndPlayReason::Type EndPlayReason)
    {
        OnActorBeginOverlap.RemoveDynamic(this, &AMyActor::OnTriggerEnter);
        OnActorEndOverlap.RemoveDynamic(this, &AMyActor::OnTriggerExit);
 
        Super:EndPlay(EndPlayReason);
    }

См. https://wiki.unrealengine.com/Simple_Global_Event_System

Standard Delegates

Делегаты позволяют вызывать функции не зная тип объекта. Делегаты следует передавать по ссылке (by reference).
Они объявляются успользуя макросы в зависимости от сигнатуры функции:

DECLARE_DELEGATE( DelegateName ) // void Function()	
DECLARE_DELEGATE_OneParam( DelegateName, Param1Type ) // void Function( <Param1> )	
DECLARE_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type ) // void Function( <Param1>, <Param2> )	
DECLARE_DELEGATE_<Num>Params( DelegateName, Param1Type, Param2Type, ... ) // void Function( <Param1>, <Param2>, ... )	
DECLARE_DELEGATE_RetVal( RetValType, DelegateName ) // <RetVal> Function()	
DECLARE_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type ) // <RetVal> Function( <Param1> )	
DECLARE_DELEGATE_RetVal_TwoParams( RetValType, DelegateName, Param1Type, Param2Type ) // <RetVal> Function( <Param1>, <Param2> )	
DECLARE_DELEGATE_RetVal_<Num>Params( RetValType, DelegateName, Param1Type, Param2Type, ... ) // <RetVal> Function( <Param1>, <Param2>, ... )	

См. подробнее https://wiki.unrealengine.com/Delegates_In_UE4,_Raw_Cpp_and_BP_Exposed

Dynamic Delegates

Динамические Делегаты позволяют вызывать функции не зная тип объекта и могут быть сериализованы, найдены по имени. Динамические Делегаты медленнее стандартных делегатов.
Они объявляются успользуя макросы:

DECLARE_MULTICAST_DELEGATE...
DECLARE_DYNAMIC_DELEGATE...
DECLARE_DYNAMIC_MULTICAST_DELEGATE...
DECLARE_DYNAMIC_DELEGATE...
DECLARE_DYNAMIC_MULTICAST_DELEGATE...

IsA, StaticClass, GetClass

Проверить каков класс объекта можно используя метод IsA:
if (OtherActor->IsA(ACharacter::StaticClass()))

Хорошей практикой также является проверка существования объекта:
if (OtherActor && OtherActor->IsA(ACharacter::StaticClass()))

StaticClass() - возвращяет UClass во время компиляции.
GetClass() - возвращяет UClass динамически.

Пример:

AActor * SomeActor = MyGamePawn;
SomeActor->StaticClass() // вернет AActor 
SomeActor->GetClass(); // вернет MyGamePawn 

см. также:

PlaySoundAtLocation, UAudioComponent, USoundBase

  • USoundBase - основной класс аудио, от него наследуются USoundCue и USoundWave.
  • USoundCue - составной настраиваемый звук.
  • USoundWave - аудио файл.
UGameplayStatics::PlaySoundAtLocation(this, OnHitSound, GetActorLocation());

В примере PlaySoundAtLocation использует для проигрывания звука по позиции объекта используя GetActorLocation().

Также в примере показана работа с UAudioComponent, который присоединяется к MeshComponent.

	UPROPERTY(EditAnyWhere)
		UAudioComponent* OnOverlapSound;
		
	...
	
	OnOverlapSound = CreateDefaultSubobject<UAudioComponent>(TEXT("PropellerAudioComp"));
	OnOverlapSound->bAutoActivate = false;
	OnOverlapSound->AttachTo(MeshComponent);
	static ConstructorHelpers::FObjectFinder<USoundBase> soundBase(
		TEXT("/Game/StarterContent/Audio/Collapse01.Collapse01")
	);
	OnOverlapSound->SetSound(soundBase.Object);
	
	...
	
		OnOverlapSound->Play();
		OnOverlapSound->SetFloatParameter(FName("pitch"), 2500.f);

OnOverlapSound->SetFloatParameter(FName("pitch"), 2500.f); задает высоту тона (Pitch). Высота тона - качество звука, определяемое человеком на слух и зависящее в основном от его частоты, т. е. от числа колебаний в секунду. С увеличением частоты растёт высота звука.
SetFloatParameter задает параметр "pitch", данный параметр мы должны создать сами в Continuous Modulator:

  1. Создайте Sound Cue на основе любого звука, в примере это Collapse01 (Sound Wave): ПКМ по Collapse01 в Content Browser, пункт Create Cue.
  2. Созданный Cue назовем Collapse01_Cue (/Game/StarterContent/Audio/Collapse01_Cue.Collapse01_Cue).
  3. Добавьте Continuous Modulator
    modulator
  4. Соедините Wave Player -> Modulator -> Output
    modulator connect
  5. Настройте Continuous Modulator: Установите В Pitch Modulation Params значение Parameter Name в "pitch" (важен регистр и отсутствие пробелов). Задайте Max Output в 4500.0
    modulator setup
  6. Сохраните все и замените в коде
	static ConstructorHelpers::FObjectFinder<USoundBase> soundBase(
		TEXT("/Game/StarterContent/Audio/Collapse01.Collapse01")
	);

на созданный Cue:

	static ConstructorHelpers::FObjectFinder<USoundBase> soundBase(
		TEXT("/Game/StarterContent/Audio/Collapse01_Cue.Collapse01_Cue")
	);

Можете также добавить проверку существования объекта:

	if (soundBase.Object->IsValidLowLevelFast()) {
		OnOverlapSound->SetSound(soundBase.Object);
	}
  • IsValidLowLevel - проверяет объект.
  • IsValidLowLevelFast - проверяет объект по указателям, работает быстрее IsValidLowLevel.
  1. Сохраните и скомпилируйте код.

Подробнее настраиваемые свойства звука описаны на https://docs.unrealengine.com/latest/INT/Engine/Audio/SoundCues/NodeReference/index.html

см. также

Упражнение: Измените громкость звука

Для этого используйте Volume Modulation Params аналогично примеру выше.

Transformation

 Mesh->SetRelativeRotation(FRotator(0,90,0));
 Mesh->SetRelativeLocation(FVector(0,0,100));
 Mesh->SetRelativeLocationAndRotation(FVector(0,0,100), FRotator(0,90,0));
 FMatrix transform = FRotationMatrix(FRotator(0,90,0));
 FMatrix IActorSpace = ActorToWorld().ToMatrixWithScale();
 FMatrix transform = FRotationTranslationMatrix(FRotator(0,90,0), FVector(0,0,100));

см. подробнее:

Упражнение: Система частиц

	UParticleSystemComponent* ParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Particles"));
	ParticleSystem->SetupAttachment(RootComponent);
	ParticleSystem->bAutoActivate = true;
	ParticleSystem->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
	if (ParticleAsset.Succeeded())
	{
		ParticleSystem->SetTemplate(ParticleAsset.Object);
	}

bAutoActivate - используется для автоматического включения системы частиц.
SetTemplate - задает шаблон системы частиц (поскольку частицы могут быть разного размера, цвета и т.д. используется шаблон).

Упражнение: Параметры системы частиц

Задайте цвет системы частиц из кода.

Для этого создайте новую систему частиц ( см. https://www.youtube.com/watch?v=G9vKSqRzhGQ&t=122s ) или скопируйте существующую.
Например, продублируем P_Fire и назовем копию P_Fire2.
Удалим из P_Fire2 все Emmiters за исключением первого Flames.
Добавим параметр Scale Color/Life.
Установим Color Scale Over Life в Distribution Vector Color Parameter
Зададим Parameter Name равным PColor.

PColor

Изменим путь ParticleAsset на созданную систему.

static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire2.P_Fire2"));

Изменим параметр PColor из кода:

ParticleSystem->SetVectorParameter("PColor", FVector(1.0f, 0.0f, 0.0f));

Подробнее на https://wiki.unrealengine.com/Particle_Instance_Parameters_Tutorial

Упражнение: Физика

В Unreal Engine любой Primitive Component (UPrimitiveComponent в C++) может быть физическим объектом. Некоторые примитивные компоненты это фигурные компоненты — ShapeComponents (капсула, сфера, куб), StaticMeshComponent и SkeletalMeshComponent.

	MeshComponent->SetSimulatePhysics(true);

SetSimulatePhysics - включает просчеты физики.
При включении физических расчетов объект может сдинуться под действием гравитации, воздействия других сил.
Добавьте систему частиц сначала только к RootComponent, а затем только к MeshComponent.
При добавлении к RootComponent система частиц останется на месте, а при добавлении к MeshComponent будет за ним следовать (т.к. SetSimulatePhysics включен только у MeshComponent).

Измените

PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);
PhysicalComp->SetLinearDamping(0.15f);
PhysicalComp->SetAngularDamping(100.0f);
PhysicalComp->SetEnableGravity(true);

Рекомендуемая литература:

  • Unreal Engine Physics Essentials

см. также:

Упражнение: RayCast, RayTrace

Можно пустить луч в мире функцией LineTraceSingle для поиска объектов на которые смотрит игрок.

APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
    // You can use this to customize various properties about the trace
    FCollisionQueryParams Params;
    // Ignore the player's pawn
    Params.AddIgnoredActor(GetPawn());
 
    // The hit result gets populated by the line trace
    FHitResult Hit;
 
    // Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)
    FVector Start = PlayerCameraManager->GetCameraLocation();
    FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
    bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
 
    if (bHit)
    {
        // Hit.Actor contains a weak pointer to the Actor that the trace hit
        return Cast<APawn>(Hit.Actor.Get());
    }
 
    return nullptr;
}

Пример с FCollisionQueryParams:

	void ATutoSacCharacter::Action()
	{
		// raycast options
		FCollisionQueryParams RV_TraceParams =
			FCollisionQueryParams(FName(TEXT("RV_Trace")), true, this);
		RV_TraceParams.bTraceComplex = true;
		RV_TraceParams.bTraceAsyncScene = true;
		RV_TraceParams.bReturnPhysicalMaterial = false;
		FHitResult RV_Hit(ForceInit);
		// get the camera position
		FVector startFire = FollowCamera->GetComponentLocation();
		// send the raycast 500 units forward
		FVector endFire = startFire + (FollowCamera->GetComponentRotation().Vector() *
			500.0f);
		// call GetWorld() from within an actor extending class
		GetWorld()->LineTraceSingleByChannel
		(
			RV_Hit, //result
			startFire, //start
			endFire, //end
			ECC_Visibility, //collision channel
			RV_TraceParams
		);
		// if we hit something
		if (RV_Hit.GetActor())
		{
			// check if it's a bag
			ABag* theBag = Cast<ABag>(RV_Hit.GetActor());
			if (theBag)
			{
				Equip(theBag);
			}
			// check if it's an item
			AItem* theItem = Cast<AItem>(RV_Hit.GetActor());
			if (theItem && carryingBag)
			{
				carryingBag->AddItem(theItem);
			}
		}
	}

Подборка полезных Trace функций: https://wiki.unrealengine.com/Trace_Functions

В Packaged Builds возможно понадобится включение Allow CPUAccess, см. http://wacki.me/blog/2017/04/ue4-smooth-trace-normal/

Упражнение: Light Components

Static Lights — Статичные источники света. Их нельзя перемещать или как-то изменять в режиме реального времени.
Lightmap Resolution — Разрешение карты теней (или карта освещения). Позволяет контролировать детализацию запеченного освещения для статичных источников света.
Stationary Lights – Стационарный источник света, который может изменять свой цвет и свою насыщенность в реальном времени, но не может перемещаться, вращаться или масштабировать силу света. Так же просчитывается отдельно, однако может отбрасывать тени от динамических объектов
Movable Lights – Полностью динамичный и.с., способный менять все свои характеристики в реальном времени и создавать полностью динамическое освещение.

см. подробнее на:

Источники освещения:

  • DirectionalLight (UDirectionalLightComponent) — Направленный источник света, который имитируют свет, идущий издалека (солнце).
    UPROPERTY(VisibleAnywhere)
    UDirectionalLightComponent* DirectionalLightComp;
    DirectionalLightComp = CreateDefaultSubobject<UDirectionalLightComponent>(TEXT("UDirectionalLightComponent"));
    DirectionalLightComp->SetupAttachment(this->RootComponent);
  • PointLight (UPointLightComponent) – обычная «лампочка». Испускает свет во все стороны из одной точки.
    UPROPERTY(VisibleAnywhere)
    UPointLightComponent* PointLightComp;
    PointLightComp = CreateDefaultSubobject<UPointLightComponent>(TEXT("UPointLightComponent"));
    PointLightComp->SetupAttachment(this->RootComponent);
  • SkyLight (USkyLightComponent) - общее освещение (Ambient) для мешей на вашем уровне.
    UPROPERTY(VisibleAnywhere)
    USkyLightComponent* SkyLightComp;
    SkyLightComp = CreateDefaultSubobject<USkyLightComponent>(TEXT("USkyLightComponent"));
    SkyLightComp->SetupAttachment(this->RootComponent);
  • SpotLight (USpotLightComponent) — прожекторный источник света, который испускает свет из одной точки конусной формы, подобно фонарику.
    UPROPERTY(VisibleAnywhere)
    USpotLightComponent* SpotLightComp;
    SpotLightComp = CreateDefaultSubobject<USpotLightComponent>(TEXT("USpotLightComponent"));
    SpotLightComp->SetupAttachment(this->RootComponent);

Создайте Actor при вхождении в который (как Trigger) будет активироваться источник освещения (UPointLightComponent) используя bAutoActivate.

	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
	PointLightComponent->Intensity = 1000.0f;
	PointLightComponent->AttenuationRadius = 20.f;
	PointLightComponent->LightColor = FColor(0.0f, 1.0f, 0.0f);
	PointLightComponent->SetVisibility(true);
	PointLightComponent->bAutoActivate = true;
	PointLightComponent->SetupAttachment(MeshComponent);
	PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));

Упражнение: Arrow Component

Arrow Component - указатель выводящийся с помощью линий и предназначенный для указания направления.

Его можно использовать, например, для создания следов игрока на песке.

#include "Components/ArrowComponent.h"
// ...
    UPROPERTY(VisibleAnywhere)
    UArrowComponent* ArrowComp;
    ArrowComp = CreateDefaultSubobject<UArrowComponent>(TEXT("UArrowComponent"));
    ArrowComp->SetupAttachment(this->RootComponent);
    ArrowComp->SetHiddenInGame(false);
    ArrowComp->ArrowColor = FColor::Blue;
    ArrowComp->ArrowSize = 0.5f;
    ArrowComp->bTreatAsASprite = true;
    ArrowComp->SpriteInfo.Category = ConstructorStatics.ID_Tooltips;
    ArrowComp->SpriteInfo.DisplayName = ConstructorStatics.NAME_Tooltips;
    ArrowComp->bIsScreenSizeScaled = true;
    ArrowComp->SetRelativeRotation(FRotator(90.0f, 0.0f, 0.0f));
    ArrowComp->Mobility = EComponentMobility::Dynamic;

Использованные материалы:

// Fill out your copyright notice in the Description page of Project Settings.
#include "SCP.h"
#include "MyActor.h"
#include <EngineGlobals.h>
#include <Runtime/Engine/Classes/Engine/Engine.h>
// https://wiki.unrealengine.com/Logs,_Printing_Messages_To_Yourself_During_Runtime#Related_Tutorial
#define print(text) if (GEngine) GEngine->AddOnScreenDebugMessage(-1, 1.5, FColor::White,text)
// Sets default values
AMyActor::AMyActor()
{
static ConstructorHelpers::FObjectFinder<USoundBase> soundBase2(
TEXT("/Game/StarterContent/Audio/Collapse01.Collapse01")
);
OnHitSound = soundBase2.Object;
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Root = CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));
RootComponent = Root;
Root->bGenerateOverlapEvents = true;
Root->SetCollisionProfileName(TEXT("OverlapAll"));
OnOverlapSound = CreateDefaultSubobject<UAudioComponent>(TEXT("PropellerAudioComp"));
OnOverlapSound->bAutoActivate = false;
OnOverlapSound->AttachTo(Root);
static ConstructorHelpers::FObjectFinder<USoundBase> soundBase(
TEXT("/Game/StarterContent/Audio/Collapse01.Collapse01")
);
OnOverlapSound->SetSound(soundBase.Object);
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
MeshComponent->SetupAttachment(RootComponent);
MeshComponent->SetHiddenInGame(false);
MeshComponent->bGenerateOverlapEvents = true;
MeshComponent->SetCollisionProfileName(TEXT("OverlapAll"));
MeshComponent->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnOverlap);
MeshComponent->OnComponentHit.AddDynamic(this, &AMyActor::OnHit);
static ConstructorHelpers::FObjectFinder<UStaticMesh> VisualAsset(
TEXT("/Game/FirstPerson/Meshes/FirstPersonProjectileMesh.FirstPersonProjectileMesh")
);
if (VisualAsset.Succeeded())
{
MeshComponent->SetStaticMesh(VisualAsset.Object);
MeshComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
MeshComponent->SetWorldScale3D(FVector(1.0f));
}
speedScale = 0.0f;
}
void AMyActor::OnOverlap(class UPrimitiveComponent* OverlappingComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
print(TEXT("Function called"));
if (OtherActor->IsA(ACharacter::StaticClass()))
{
OtherActor->SetActorLocation(OtherActor->GetActorLocation() + FVector(550.0f));
OnOverlapSound->Play();
OnOverlapSound->SetFloatParameter(FName("pitch"), 2500.f);
}
}
void AMyActor::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Railplayer hit %d"), 2));
if (OnHitSound)
{
UGameplayStatics::PlaySoundAtLocation(this, OnHitSound, GetActorLocation());
}
}
// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMyActor::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
FVector NewLocation = GetActorLocation();
float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime)) * speedScale;
NewLocation.Y += DeltaHeight * 20.0f; //Scale our height by a factor of 20
RunningTime += DeltaTime;
SetActorLocation(NewLocation);
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class SCP_API 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;
UPROPERTY(EditAnyWhere)
UShapeComponent* Root;
UPROPERTY(EditAnyWhere)
UStaticMeshComponent* MeshComponent;
UPROPERTY(EditAnyWhere)
UAudioComponent* OnOverlapSound;
UPROPERTY(EditAnyWhere)
USoundBase* OnHitSound;
UPROPERTY(EditAnyWhere)
float speedScale = 10.0f;
float RunningTime = 0;
UPROPERTY(BlueprintAssignable, Category = "Collision")
FComponentBeginOverlapSignature OnComponentOverlap;
protected:
UFUNCTION()
void OnOverlap(class UPrimitiveComponent* OverlappingComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
};
@derofim
Copy link
Author

derofim commented Apr 30, 2017

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment