Archives Marzec 2021

PlayerProjectile: Tworzenie Child

Zamierzamy stworzyć jeszcze dwa rodzaje projectiles, z których będzie mógł korzystać gracz. Zostaną również zaimplementowane jako child Blueprints klasy PlayerProjectile C++.

Wykonamy kopię Blueprint pierwszego typu pocisku, aby skorzystać z wykonanej w nim konfiguracji.

Content Browser, przejdź do FirstPersonBP / Blueprints folderu i kliknij prawym przyciskiem myszy na Blueprint BP_PlayerProjectile1. Wybierz opcję Duplicate i wpisz nazwę BP_PlayerProjectile2 dla nowego Blueprints.

Kliknij component StaticMesh na karcie Components. Właściwości StaticMesh zostaną wyświetlone na karcie Details. Zmieńmy kształt pocisku. Wybierz Shape_Pipe dla Static Mesh i M_Metal_Copper dla Material. W Rotation umieść 90,0 w Z i zmień scale na 0,5 w X, Y i Z.

Kliknij komponent Projectile movement na karcie Components. Na karcie Details zmień wartości Initial Speed i Max Speed na 6000. To podwoi prędkość tego pocisku.

To jedyne zmiany w BP_PlayerProjectile2, zapisz Blueprint.

Zrób kolejną kopię BP_PlayerProjectile1 używając opcji Duplicate i wpisz nazwę BP_PlayerProjectile3 dla nowego Blueprint. Ten pocisk będzie symulował granat. Będzie miał większą wagę, więc gracz będzie musiał strzelać w górę, a wybuchnie, gdy się z czymś zderzy. We właściwościach StaticMesh zmień Material na M_Tech_Hex_Tile_Pulse i zachowaj tę samą statyczną siatkę i skalę jak w BP_PlayerProjectile1.

We właściwości Initial Speed zmień Projectile Gravity Scale na 3.0 .

Na Event Graph Blueprint zmodyfikuj Event Hit, aby uzyskać działania pokazane na poniższym screenie. W akcji SpawnActor wybierz opcję Blueprint Effect Explosion w parametrze Class. Kliknij prawym przyciskiem myszy parametr Spawn Transform i wybierz opcję Podziel sworzeń konstrukcji. Zmień skalę transformacji pojawiania się na 5,0 na X, Y i Z.

Skompiluj i zapisz Blueprint. Zakończyliśmy wdrażanie PlayerProjectile. W następnym artykule zaczniemy implementować PlayerCharacter, który będzie używał PlayerProjectile.


Źródło:https://romeroblueprints.blogspot.com/2021/03/playerprojectile-creating-more-child.html

PlayerProjectile: Rozszerzenie klasy C ++ w Blueprint

W tym artykule utworzymy pierwszy Blueprint child oparty na klasie PlayerProjectile C++.

Content Browser , przejdź do folderu TutoPart3, który znajduje się wewnątrz klas C++ folderu. Kliknij prawym przyciskiem myszy klasę PlayerProjectile i wybierz opcję Create Blueprint class based on PlayerProjectile, jak pokazano na poniższej ilustracji.. 

Na następnym ekranie wpisz BP_PlayerProjectile1 w polu Name. W polu Path wybierz folder Blueprints znajdujący się w folderze FirstPersonBP i kliknij przycisk Create Blueprint Class.

Kliknij dwukrotnie, aby otworzyć Blueprint Editor. Zobacz, że na karcie Components są komponenty, które zdefiniowaliśmy w klasie C ++ zostały odziedziczone przez Blueprint. Wybierz składnik StaticMesh, aby zdefiniować zasób .

Karta Details przedstawia właściwości wybranego składnika StaticMesh. Wybierz FirstPersonProjectileMesh oraz zmień Skalę na 0,1 w X, Y i Z.

Na Event Graph dodaj akcję Event Hit oraz DestroyActor, jak pokazano na poniższej ilustracji. Instancja tego schematu zostanie usunięta z gry, gdy zderzy się z czymś.


Źródło:https://romeroblueprints.blogspot.com/2021/03/playerprojectile-extending-c-class-in.html

Tworzenie Interfaces w C++

en artykuł nie dotyczy interfejsu użytkownika (UI). W programowaniu istnieje koncepcja zwana interfejsem , która jest typem zawierającym funkcje, które muszą być implementowane przez klasy dziedziczące po interfejsie. Działa jako standardowy protokół komunikacyjny między różnymi typami klas.

Aby lepiej zrozumieć, stwórzmy prosty interfejs, którego można używać w kodzie C ++.

Wyszukiwarce bibliotek przejdź do folderu zawierającego klasy C ++. Kliknij prawym przyciskiem myszy wolne miejsce i wybierz opcję New C++ Class , jak pokazano na poniższym obrazku.

Na następnym ekranie musisz wybrać Unreal Interface jako klasę nadrzędną i kliknąć przycisk Next.

W polu Name wpisz Interactable. W polu Path zachowaj domyślny folder projektu. Kliknij przycisk Create Class.

Przyjrzyjmy się kodowi C ++ wygenerowanemu przez silnik Unreal Engine dla interfejsu interaktywnego. Dodałem tylko jedną linię z deklaracją funkcji Interact () . Oto zawartość pliku Interactable.h:

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Interactable.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UInteractable : public UInterface
{
  GENERATED_BODY()
};


class TUTOFIRSTPERSON_API IInteractable
{
  GENERATED_BODY()

public:

  virtual void Interact(AActor* OtherActor) = 0;
};

Zauważ, że zostały zdefiniowane dwie klasy, UInteractable i IInteractable . W UInteractable klasa dziedziczy z UInterface i wykorzystuje UINTERFACE () makro. Ta klasa nie wymaga modyfikacji i istnieje tylko po to, aby interfejs był widoczny dla systemu Reflection silnika Unreal Engine.

IInteractable klasa to taka, która naprawdę reprezentuje interfejs i będą dziedziczone przez innych klas. W tej klasie deklarowane są funkcje interfejsu.

Wirtualnego kluczowe wykorzystane przed Interact () środki funkcyjne, że funkcja ta może być pominięte. Gdy deklaracja funkcji wirtualnej kończy się na „ = 0 ”, jest to czysta funkcja wirtualna , czyli nie ma implementacji w klasie bazowej.

Przykładowe użycie:

Rozwińmy przykład utworzony w poprzednim artykule:

Zmieńmy WallSconce klasę tak, że realizuje Interactable interfejs, który został utworzony powyżej. Aby to zrobić, klasa WallSconce musi dziedziczyć z klasy IInteractable .

Usuń funkcję PressSwitch () i dodaj deklarację funkcji Interact () . Plik WallSconce.h wygląda następująco:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interactable.h"
#include "WallSconce.generated.h"

UCLASS()
class AWallSconce : public AActor, public IInteractable
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AWallSconce();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
	
	UPROPERTY(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	virtual void Interact(AActor* OtherActor) override;
};

W pliku WallSconce.cpp usuń funkcję PressSwitch () i dodaj definicję funkcji Interact () :

#include "WallSconce.h"
#include "Components/PointLightComponent.h"

// Sets default values
AWallSconce::AWallSconce()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(
                                              TEXT("StaticMeshComponent"));
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Props/SM_Lamp_Wall.SM_Lamp_Wall"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 1.f, 1.f));
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 30.0f));
}

void AWallSconce::Interact(AActor* OtherActor)
{
  PointLightComponent->ToggleVisibility();
  
  FString Message = FString::Printf(TEXT("Switch pressed by %s"), 
                                    *(OtherActor->GetName()));

  if(GEngine)
  {
    GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
  }
}

// Called when the game starts or when spawned
void AWallSconce::BeginPlay()
{
  Super::BeginPlay();	
}

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

Użyjemy tego samego mapowania danych wejściowych, co w poprzednim artykule, w którym używany jest klawisz E.

Dokonamy dwóch poprawek w pliku TutoFirstPersonCharacter.cpp (ta nazwa zależy od nazwy twojego projektu). Na początku pliku zamień #include z „WallSconce.h” na „Interactable.h” :

#include "Interactable.h"

W funkcji InteractWithWorld () zamień kod wewnątrz bloku if (bHitSomething) , jak pokazano poniżej:

void ATutoFirstPersonCharacter::InteractWithWorld()
{
  float LengthOfTrace = 300.f;
	
  FVector StartLocation;
  FVector EndLocation;
  
  StartLocation = FirstPersonCameraComponent->GetComponentLocation();
  
  EndLocation = StartLocation + 
    (FirstPersonCameraComponent->GetForwardVector() * LengthOfTrace);
	
  FHitResult OutHitResult;
  FCollisionQueryParams LineTraceParams;  
   
  bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult,
                       StartLocation, EndLocation, ECC_Visibility, LineTraceParams);

  if(bHitSomething)
  {
    
    IInteractable* InteractableObject = Cast<IInteractable>(OutHitResult.GetActor());
	
    if(InteractableObject)
    {
      InteractableObject->Interact(this);
    }
  }
}

Wykonano Cast <IInteractable>, aby sprawdzić, czy aktor znaleziony przez Line Trace implementuje interfejs IInteractable . Jeśli rzutowanie powiedzie się, zmienna InteractableObject otrzymuje prawidłowe odwołanie, którego można użyć do wywołania funkcji interfejsu.

TutoFirstPersonCharacter klasa ma już odniesienia do WallSconce klasie. Odwołuje się tylko do interfejsu interaktywnego . Jeśli utworzysz inną klasę, która implementuje interfejs Interactable , nie jest konieczne modyfikowanie kodu klasy TutoFirstPersonCharacter w celu interakcji z nową klasą.

Skompiluj kod C ++. Jeśli zrobiłeś przykład z poprzedniego artykułu, masz już instancję WallSconce na ścianie. Uruchom grę i wejdź w interakcję z WallSconce za pomocą klawisza E.

Ten artykuł kończy drugą część samouczków Unreal C ++.


Źródło:https://romeroblueprints.blogspot.com/2021/01/creating-interfaces-in-c.html

Używanie Line Traces w C++

Ślady linii służą do testowania kolizji przy użyciu zdefiniowanego segmentu linii. Istnieje kilka rodzajów śladów linii. Śledzenie linii może być oparte na kanale (widoczność lub kamera) lub na typie obiektu (WorldStatic, WorldDynamic, Pawn, …). 

Funkcje śledzenia linii są zdefiniowane w klasie UWorld . Istnieją funkcje, które zwracają tylko pierwszy znaleziony obiekt i inne funkcje, które zwracają wiele obiektów, które zderzyły się z segmentem linii.

W tym artykule skupimy się na jednej z funkcji śledzenia linii. Funkcja to LineTraceSingleByChannel(), która jest oparta na kanale i zwraca pierwszy znaleziony obiekt. Wywołanie funkcji w przykładowym kodzie wygląda następująco:

bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult,
                                                          StartLocation,
                                                          EndLocation,
                                                          ECC_Visibility,
                                                          LineTraceParams);

Spójrzmy na każdą część tego wywołania funkcji:

  • bHitSomething :  zmienna logiczna, która przechowuje wartość zwracaną przez funkcję. Otrzymuje wartość true, jeśli śledzenie linii zderzyło się z obiektem.
  • GetWorld () -> LineTraceSingleByChannel () :  Funkcja GetWorld () zwraca wskaźnik typu UWorld, który reprezentuje bieżący poziom. Funkcja Line Trace jest wywoływana za pomocą tego wskaźnika.
  • OutHitResult :  zmienna typu FHitResult . Zostanie wypełniony różnymi informacjami związanymi ze znalezionym obiektem.
  • StartLocation :  FVector  definiujący początek odcinka linii używanego w teście kolizji.
  • EndLocation :  FVector  definiujący koniec segmentu linii.
  • ECC_Visibility :  Ta stała wskazuje, że kanał Visibility będzie używany do testu zderzenia.
  • LineTraceParams :  zmienna typu FCollisionQueryParams, którą można wypełnić wartościami parametrów, które zostaną przekazane do funkcji śledzenia linii. 

Przykładowe użycie:

W tym przykładzie będziesz potrzebować projektu C ++, który ma szablon First Person z Starter Conten . Stworzyłem projekt o nazwie TutoFirstPerson .

Zmodyfikujmy postać gracza, aby używał linii śledzenia do interakcji z kinkietem ściennym. Postać Gracza musi patrzeć w kierunku kinkietu, który musi być mniej niż 3 metry dalej. Po naciśnięciu klawisza E oświetlenie kinkietu włącza się lub wyłącza.

Utwórz klasę C ++ o nazwie WallSconce, używając klasy Actor jako klasy nadrzędnej. Ta klasa zawiera StaticMeshComponent i PointLightComponent . Posiada również funkcję PressSwitch (), która będzie używana przez odtwarzacz do włączania i wyłączania światła. Plik WallSconce.h ma następującą zawartość:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WallSconce.generated.h"

UCLASS()
class AWallSconce : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AWallSconce();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
	
	UPROPERTY(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	void PressSwitch();
};

WallSconce klasa używa Static Mesh SM_Lamp_Wall . W konstruktorze mamy konfigurację komponentów Static Mesh i Point Light. Funkcja PressSwitch () wywołuje funkcję ToggleVisibility () PointLightComponent . Oto zawartość pliku WallSconce.cpp:

#include "WallSconce.h"
#include "Components/PointLightComponent.h"

// Sets default values
AWallSconce::AWallSconce()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(
                                              TEXT("StaticMeshComponent"));
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Props/SM_Lamp_Wall.SM_Lamp_Wall"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 1.f, 1.f));
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 30.0f));
}

void AWallSconce::PressSwitch()
{
  PointLightComponent->ToggleVisibility();
}

// Called when the game starts or when spawned
void AWallSconce::BeginPlay()
{
  Super::BeginPlay();	
}

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

Teraz utworzymy mapowanie wejściowe o nazwie Interakcja, które zostanie uruchomione, gdy gracz naciśnie klawisz E.

W edytorze poziomów wejdź do menu Edit-> Project Settings … i w kategorii Engine wybierz opcję Input. Kliknij symbol + obok pozycji Odwzorowania akcji , wprowadź nazwę Interakcja dla nowego mapowania akcji i wybierz klawisz E. Do mapowania akcji dla projektu będzie wyglądać następująco:

Otwórz plik nagłówkowy klasy Character utworzonej przez szablon First Person . Nazwa pliku w moim przykładowym projekcie to TutoFirstPersonCharacter.h. Dodaj deklarację funkcji InteractWithWorld() poniżej funkcji SetupPlayerInputComponent():

 // APawn interface
  virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) 
                                                                 override;
  // End of APawn interface

  void InteractWithWorld();

W pliku Character cpp musisz dodać #include na początku pliku:

#include "WallSconce.h"

W SetupPlayerInputComponent () funkcji, Interact Wejście będzie związany z InteractWithWorld () funkcji. Zmień nazwę ATutoFirstPersonCharacter na nazwę klasy Character twojego projektu i nie zapomnij umieścić operatora & przed nazwą. Służy do zwracania adresu pamięci funkcji.

PlayerInputComponent->BindAction("Interact", IE_Pressed, this,
                                 &ATutoFirstPersonCharacter::InteractWithWorld);

Na końcu pliku cpp utwórz funkcję InteractWithWorld () z poniższym kodem. Nie zapomnij zmienić nazwy klasy, która znajduje się przed operatorem „ :: ”.

void ATutoFirstPersonCharacter::InteractWithWorld()
{
  float LengthOfTrace = 300.f;
	
  FVector StartLocation;
  FVector EndLocation;
  
  StartLocation = FirstPersonCameraComponent->GetComponentLocation();
  
  EndLocation = StartLocation + 
    (FirstPersonCameraComponent->GetForwardVector() * LengthOfTrace);
	
  FHitResult OutHitResult;
  FCollisionQueryParams LineTraceParams;  
   
  bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult, 
                       StartLocation, EndLocation, ECC_Visibility, LineTraceParams);

  if(bHitSomething)
  {
    
    AWallSconce* WallSconceInstance = Cast<AWallSconce>( OutHitResult.GetActor() );
	
    if(WallSconceInstance)
    {
      WallSconceInstance->PressSwitch();
    }
  }
}

Wykonano rzutowanie <AWallSconce> w celu sprawdzenia, czy aktor znaleziony przez śledzenie linii należy do klasy AWallSconce . Jeśli tak, to zostanie wywołana funkcja PressSwitch () klasy AWallSconce .

Skompiluj kod C ++. Umieść wystąpienie WallSconce na jednej ze ścian. Uruchom grę, przejdź do WallSconce i spójrz w jego stronę. Naciśnij klawisz E , aby włączyć lub wyłączyć światło.


Źródło: https://romeroblueprints.blogspot.com/2021/01/using-line-traces-in-c.html

Tworzenie Delegates w C++

W tym artykule pokaże ci jak tworzyć delegates w C ++. W prosty sposób możemy zdefiniować Delegata jako odniesienie do funkcji.

Unreal Engine ma szeroką gamę typów delegatów, co utrudnia zrozumienie wszystkich tych opcji.

Z tego powodu w tym artykule skupimy się na użytecznym przykładzie funkcjonalnym, który możesz dostosować do swojego projektu.

W pierwszej części artykułu przedstawię kroki niezbędne do korzystania z Delegata, pokazując fragmenty przykładowego kodu. W przykładowym użyciu zobaczysz pełny przykładowy kod.

W tym przykładzie utworzymy klasę o nazwie APlatformTrigger . Gdy Aktor nakłada się na wystąpienie APlatformTrigger , delegat zostanie użyty do powiadomienia, że ​​platforma została aktywowana. Inne klasy mogą wiązać funkcje z delegatem, które zostaną wykonane po aktywowaniu platformy.

Oto kroki niezbędne do utworzenia delegata:

Krok 1 – Utwórz typ delegata

Unreal Engine ma kilka makr, które są używane do tworzenia typu delegata. Poniższy kod utworzył typ delegata o nazwie FDelegateTrigger . 

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDelegateTrigger);

To makro musi znajdować się w pliku nagłówkowym klasy APlatformTrigger powyżej makra UCLASS () . Ten delegat jest DYNAMICZNY, aby umożliwić wiązanie w Blueprints i MULTICAST, aby umożliwić powiązanie więcej niż jednej funkcji. 

Krok 2 – Zdefiniuj zmienną przy użyciu utworzonego typu delegata

Poniższy wiersz definiuje zmienną typu FDelegateTrigger . Makro UPROPERTY (BlueprintAssignable) jest używane, aby umożliwić powiązanie w Blueprints. 

UPROPERTY(BlueprintAssignable)
FDelegateTrigger OnPlatformTriggered;

Krok 3 – Uruchom delegata

W naszym przykładzie delegat zostanie wykonany, gdy platforma się nakłada. Odbywa się to przez wywołanie funkcji Broadcast () zmiennej Delegate.

OnPlatformTriggered.Broadcast();

Kolejne kroki są wykonywane w innej klasie C ++. W tym przykładzie jest to klasa o nazwie AExplosionHandle . 

Krok 4 – Zdefiniuj wskaźnik do klasy, która ma delegata

Wskaźnik jest zdefiniowany jako UPROPERTY (), dzięki czemu instancję APlatformTrigger można wybrać w edytorze poziomów. 

UPROPERTY(EditAnywhere, Category = "Delegate Test")
APlatformTrigger* PlatformTriggerInstance;

Krok 5 – Zdefiniuj UFUNCTION (), który będzie powiązany z Delegatem

W tym przykładzie jest funkcja o nazwie Explode () , która aktywuje system cząstek. 

UFUNCTION()
void Explode();

Krok 6 – Powiązanie funkcji z delegatem

Wiązanie jest wykonywane w funkcji BeginPlay () . Wskaźnik PlatformTriggerInstance służy do wywoływania funkcji AddDynamic () zmiennej delegata OnPlatformTriggered . 

PlatformTriggerInstance->OnPlatformTriggered.AddDynamic(this,
                                                        &AExplosionHandle::Explode);

Kiedy Delegat APlatformTrigger klasy jest wykonywany, Explode () funkcja AExplosionHandle klasie zostanie wywołana.

W przykładowym użyciu zobaczymy również, jak łączyć się za pomocą schematów.

Przykładowe użycie:

W tym przykładzie będziesz potrzebować projektu C ++, który ma szablon trzeciej osoby z zawartością startową .

Utwórz klasę C ++ o nazwie PlatformTrigger, używając klasy Actor jako klasy nadrzędnej. Plik PlatformTrigger.h zawiera makro, które tworzy typ Delegate FDelegateTrigger i zmienną zdefiniowaną za pomocą tego typu:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PlatformTrigger.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDelegateTrigger);

UCLASS()
class TUTOPROJECT_API APlatformTrigger : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	APlatformTrigger();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
	
	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
	
	UPROPERTY(BlueprintAssignable)
	FDelegateTrigger OnPlatformTriggered;
};

PlatformTrigger klasa używa Static Mesh Shape_Cylinder . Wysokość zmniejszono, zmieniając skalę na 0,1 na osi Z. Delegat jest wykonywany w funkcji NotifyActorBeginOverlap () . Oto zawartość pliku PlatformTrigger.cpp:

#include "PlatformTrigger.h"

// Sets default values
APlatformTrigger::APlatformTrigger()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;
	
  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  StaticMeshComponent->SetCollisionResponseToAllChannels(
                                      ECollisionResponse::ECR_Overlap);
  StaticMeshComponent->SetRelativeScale3D(FVector(1.0f, 1.0f, 0.1f));
}


void APlatformTrigger::NotifyActorBeginOverlap(AActor* OtherActor)
{
  Super::NotifyActorBeginOverlap(OtherActor);
	
  OnPlatformTriggered.Broadcast();
}

// Called when the game starts or when spawned
void APlatformTrigger::BeginPlay()
{
  Super::BeginPlay();			
}

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

Utwórz kolejną klasę C ++ o nazwie ExplosionHandle, używając klasy Actor jako klasy nadrzędnej. Ta klasa ma wskaźnik do klasy PlatformTrigger i ma funkcję UFUNCTION (), która zostanie powiązana z delegatem. Plik ExplosionHandle.h wygląda następująco:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ExplosionHandle.generated.h"

UCLASS()
class TUTOPROJECT_API AExplosionHandle : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AExplosionHandle();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;	

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;
	
	UPROPERTY(VisibleAnywhere)	
	class UParticleSystemComponent* ParticleSystemComponent;
	
	UPROPERTY(EditAnywhere, Category = "Delegate Test")
	class APlatformTrigger* PlatformTriggerInstance;
	
	UFUNCTION()
	void Explode();
};

W konstruktorze mamy konfigurację układu cząstek, który reprezentuje eksplozję. W funkcji BeginPlay () funkcja Explode () jest powiązana z delegatem. Nie zapomnij dodać niezbędnego #include . Oto zawartość pliku ExplosionHandle.cpp:

#include "ExplosionHandle.h"
#include "PlatformTrigger.h"
#include "Particles/ParticleSystemComponent.h"

// Sets default values
AExplosionHandle::AExplosionHandle()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  // RootComponent initialization 
  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;
  
  // ParticleSystemComponent initialization
  ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(
                              TEXT("ParticleSystemComponent"));
  ParticleSystemComponent->SetupAttachment(RootScene);
  ParticleSystemComponent->SetAutoActivate(false);
  
  ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleTemplate(
    TEXT("/Game/StarterContent/Particles/P_Explosion.P_Explosion"));

  if (ParticleTemplate.Succeeded())
  {
    ParticleSystemComponent->SetTemplate(ParticleTemplate.Object);
  }
  
}

void AExplosionHandle::BeginPlay()
{
  Super::BeginPlay();
	
  if(PlatformTriggerInstance)
  {
    PlatformTriggerInstance->OnPlatformTriggered.AddDynamic(this,
&AExplosionHandle::Explode);
  }
	
}

void AExplosionHandle::Explode()
{
  ParticleSystemComponent->Activate();	
}

void AExplosionHandle::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
}

Skompiluj kod C ++ i dodaj wystąpienie PlatformTrigger na poziomie. Dodaj instancję ExplosionHandle w pobliżu platformy i na karcie Szczegóły instancji w kategorii Delegate Test wybierz instancję PlatformTrigger .

Zacząć gre. Kiedy nachodzisz na platformę z Postacią, wybuch zostanie aktywowany.

Teraz powiążmy niestandardowe zdarzenie z delegatem za pomocą schematów.

Wybierz wystąpienie PlatformTrigger, które znajduje się na poziomie i otwórz Blueprint poziomu.

Kliknij prawym przyciskiem myszy na wykresie zdarzeń i wybierz opcję  Utwórz odniesienie do PlatformTrigger .

Przeciągnij z niebieskiej szpilki węzła i upuść go na wykresie zdarzeń, aby wyświetlić menu kontekstowe. Wyszukaj platformę i wybierz opcję Assign On Platform Triggered . Spowoduje to dodanie węzła Bind i nowego zdarzenia niestandardowego.

Dodaj Event BeginPlay i połącz się z węzłem Bind Event . Dodaj czynność Drukuj ciąg i połącz się z węzłem zdarzenia niestandardowego. Umieść wiadomość w węźle Print String i skompiluj Level Blueprint. Ostateczny schemat wygląda następująco:

Uruchom ponownie grę. Gdy platforma jest aktywna, delegat wywoła funkcję, która aktywuje eksplozję, a także wywoła niestandardowe zdarzenie zdefiniowane w Blueprints: 


Źródło:https://romeroblueprints.blogspot.com/2021/01/creating-delegates-in-c.html

Class Reference oraz TSubclassOf

Czasami musimy utworzyć zmienną, która przechowuje odniesienie do klasy, a nie do instancji tej klasy. Na przykład klasa reprezentująca broń może mieć zmienną, która zostanie użyta do zdefiniowania klasy pocisku, który zostanie wystrzelony przez tę broń.

Unreal Engine posiada szablon klasy o nazwie TSubclassOf, którego można używać do deklarowania zmiennych odwołujących się do klas. Ten szablon zapewnia bezpieczeństwo typu i zezwala tylko na odwołania do określonej klasy lub jej podklas.

Poniższy kod definiuje zmienną za pomocą TSubclassOf, którą można modyfikować w edytorze:

UPROPERTY(EditAnywhere, Category="TSubclassOf Example")
TSubclassOf<ATutoProjectCollectable> CollectableClass;

Poniższy obrazek pokazuje, jak ta zmienna jest reprezentowana w edytorze. Wybór zajęć jest ograniczony do dozwolonych klas.

Przykładowe użycie:

W tym przykładzie potrzebujesz projektu z  zawartością startową . 

Mamy zamiar stworzyć dwóch Actors, PickupSpawner i PickupActorPickupSpawner tworzy instancję PickupActor na początku gry. W edytorze poziomów możemy wybrać podklasę PickupActor, która zostanie utworzona przez instancję PickupSpawner. 

Utwórz klasę C ++ o nazwie PickupActor, używając klasy Actor jako klasy nadrzędnej. Uprośćmy tę klasę za pomocą statycznej siatki do wizualnej reprezentacji. Plik PickupActor.h powinien mieć następującą zawartość:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PickupActor.generated.h"

UCLASS()
class TUTOPROJECT_API APickupActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	APickupActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
};

PickupActor klasa używa Static Mesh Shape_Cone . Plik PickupActor.cpp wygląda następująco:

#include "PickupActor.h"

// Sets default values
APickupActor::APickupActor()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
}

// Called when the game starts or when spawned
void APickupActor::BeginPlay()
{
  Super::BeginPlay();	
}

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

Utwórz klasę C ++ o nazwie PickupSpawner, używając klasy Actor jako klasy nadrzędnej. Ta klasa zawiera zmienną zdefiniowaną jako TSubclassOf , która pozwoli na wybór innych podklas, które mają być przechowywane w zmiennej. Oto zawartość pliku PickupSpawner.h:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PickupActor.h"
#include "PickupSpawner.generated.h"

UCLASS()
class TUTOPROJECT_API APickupSpawner : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	APickupSpawner();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;

	UPROPERTY(EditAnywhere, Category=Configuration)
	TSubclassOf<APickupActor> PickupClass;
};

Spawn z APickupActor odbywa się w BeginPlay() funkcji przy użyciu klasy, który jest przechowywany w PickupClass zmiennej. Oto zawartość pliku PickupSpawner.cpp:

#include "PickupSpawner.h"

// Sets default values
APickupSpawner::APickupSpawner()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;
	
  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Architecture/SM_AssetPlatform.SM_AssetPlatform"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
}

// Called when the game starts or when spawned
void APickupSpawner::BeginPlay()
{
  Super::BeginPlay();
		
  if(PickupClass)
  {
    FVector SpawnLocation = GetActorLocation() + FVector(0.0f, 0.0f, 50.0f);
	
    FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
	
    GetWorld()->SpawnActor<APickupActor>(PickupClass, SpawnLocation, SpawnRotation );
  }
}

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

Skompiluj kod C ++ i dodaj trzy wystąpienia PickupSpawner na poziomie.

Zamierzamy stworzyć dwie podklasy PickupActor używając Blueprints tylko po to, aby zmienić StaticMesh.

Kliknij prawym przyciskiem myszy klasę PickupActor i wybierz opcję  Create Blueprint Class Based on PickupActor , jak pokazano na poniższym obrazku.

Umieść nazwę BP_PickupCube na nowym Blueprint. Na karcie Składniki wybierz StaticMeshComponent .

W zakładce Szczegóły wybierz Static Mesh Shape_Cube i skompiluj Blueprint.

Wykonaj te same kroki, aby utworzyć kolejny Blueprint o nazwie BP_PickupCapsule i Static Mesh Shape_NarrowCapsule.

Wybierz jedną z instancji PickupSpawner na tym poziomie. Na karcie Details w kategorii Konfiguracja zmień wartość PickupClass na BP_PickupCapsule . 

W innym wystąpieniu PickupSpawner wybierz BP_PickupCube. Uruchom grę i zobacz, jak instancje PickupSpawner tworzą różne typy PickupActor.  


Źródło:https://romeroblueprints.blogspot.com/2021/01/class-reference-and-tsubclassof.html

TMap: Maps w C++

TMap  to kolejny typ kontenera szeroko stosowany w Unreal Engine. Elementy mapy to pary klucz-wartość. Typ klucza może różnić się od typu wartości. Duplikaty kluczy są niedozwolone. Wyszukiwanie elementów odbywa się za pomocą wartości klucza.

Ten kod tworzy TMap, który można modyfikować w edytorze. Typ klucza to FString, a typ wartości to float .

UPROPERTY(EditAnywhere, Category=MapExample)
TMap<FString, float> PriceTable;

Aby dodać elementy do TMap , użyj funkcji Add () przekazującej klucz i wartość jako parametry.

PriceTable.Add( TEXT("Axe"), 37.5f );
PriceTable.Add( TEXT("Hammer"), 25.0f );
PriceTable.Add( TEXT("Spear"), 18.0f );

Użyj funkcji Remove () przekazującej klucz jako parametr, aby usunąć element z TMap.

PriceTable.Remove( TEXT("Hammer") );

Funkcja Empty () usuwa wszystkie elementy z mapy TMap.

PriceTable.Empty();

Liczbę elementów w TMap uzyskuje się za pomocą funkcji Num().

int32 NumberOfElements = PriceTable.Num();

Funkcja Find () przyjmuje klucz jako parametr i zwraca wskaźnik do wartości skojarzonej z kluczem. Jeśli klucz nie zostanie znaleziony, zwracany jest pusty wskaźnik (nullptr).

float* PtrPrice = PriceTable.Find( TEXT("Spear") );

Iterowanie po TMap można wykonać za pomocą pętli for opartej na zakresie . Element TMap jest typu TPair i zawiera zmienne Klucz i Wartość . Dla uproszczenia możesz użyć słowa kluczowego auto w C ++ razem z operatorem & , aby zdefiniować odwołanie do elementu TMap, jak pokazano w poniższym kodzie.

for (auto& ItemPrice : PriceTable)
{
  UE_LOG(LogTemp, Warning, TEXT("%s - %.2f"), *(ItemPrice.Key), ItemPrice.Value);
}

Znak specjalny % s zostanie zastąpiony zawartością zmiennej ItemPrice.Key . Konieczne jest umieszczenie operatora * przed zmiennymi typu FString . Wartość ItemPrice.Value zastąpi znaki % .2f i zostanie wyświetlona z dwoma cyframi dziesiętnymi.

Przykładowe użycie:

Utwórz klasę C ++ o nazwie TestTMap, używając klasy Actor jako klasy nadrzędnej. W pliku TestTMap.h dodaj deklarację komponentów i TMap:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestTMap.generated.h"

UCLASS()
class TUTOPROJECT_API ATestTMap : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATestTMap();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMesh;
	
	UPROPERTY(EditAnywhere, Category=MapExample)
	TMap<FString, float> PriceTable;
};

W funkcji BeginPlay() zostanie użyta pętla for oparta na zakresie, która zapisuje na ekranie nazwę i cenę elementów TMap . Zawartość pliku TestTMap.cpp wygląda następująco:

#include "TestTMap.h"

ATestTMap::ATestTMap()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMesh->SetupAttachment(RootScene);
}

// Called when the game starts or when spawned
void ATestTMap::BeginPlay()
{
  Super::BeginPlay();

  FString Message;
  
  if(PriceTable.Num() > 0)
  {
    Message = FString::Printf(TEXT("Number of elements in the Map: %d"),
                              PriceTable.Num());

    if(GEngine)
    {	
     GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
	  
	  
     for(auto& ItemPrice : PriceTable)
     {
      Message = FString::Printf(TEXT("%s - %.2f"), *(ItemPrice.Key), ItemPrice.Value);
		
      GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
     }
    }	  
  }	    
}

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

Skompiluj kod C ++ i dodaj instancję TestTMap na poziomie. Dodaj elementy do TMap, korzystając z karty Details instancji. Nie będziesz w stanie dodać duplikatu klucza.

Uruchom grę i zobacz na ekranie nazwy i ceny elementów dodanych do mapy:


Źródło: https://romeroblueprints.blogspot.com/2021/01/tmap-maps-in-c.html

For Loops oraz TActorRange

W języku C ++ pętla jest używana do powtórzenia bloku kodu, gdy określony warunek jest prawdziwy. Jednym z rodzajów pętli jest instrukcja for.

Poniższy kod przedstawia przykład użycia instrukcji for. W TotalEnemies przechowuje zmienna liczba wrogów, które muszą być tworzone na poziomie. Licznik zmiennej jest zwiększana z każdym wykonaniem dla pętli. Blok kodu pętli for zostanie powtórzony, gdy wartość Counter jest mniejsza niż wartość TotalEnemies. Dziesięć instancji klasy AEnemyActor zostanie utworzonych w losowych miejscach na poziomie.  

int32 TotalEnemies = 10;
int32 Counter;
float XPosition;
float YPosition;
	
FVector  SpawnLocation;
FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
	
for(Counter = 0; Counter < TotalEnemies; Counter++)
{
  XPosition = FMath::RandRange(-1000, 1000);
  YPosition = FMath::RandRange(-1000, 1000);
		
  SpawnLocation = FVector(XPosition, YPosition, 130.0f);
		
  GetWorld()->SpawnActor<AEnemyActor>(AEnemyActor::StaticClass(),
                                      SpawnLocation, SpawnRotation );
}

Instrukcja for ma odmianę zwaną pętlą for opartą na zakresie . W tym typie for deklarowana jest zmienna przechowująca bieżący element grupy elementów. Blok kodu będzie powtarzany, aż wszystkie elementy zostaną użyte. Pętla for oparta na zakresie ma następujący format:

for(variable : range_expression) 
{

}

Kontenery Unreal TArray , TMap i TSet współpracują z pętlą for opartą na zasięgu.

Unreal Engine ma szablon C++ o nazwie TActorRange, który zawiera wyrażenie zakresu reprezentujące wystąpienia danej klasy, które są obecne na poziomie. Poniższa pętla for oparta na zakresie wykorzystuje TActorRange do przełączania widoczności wszystkich instancji klasy APointLight na poziomie.

for (APointLight* PointLightInstance : TActorRange<APointLight>( GetWorld() ) )
{	
  PointLightInstance->GetLightComponent()->ToggleVisibility();
}

APointLight klasa zawiera LightComponent który ma ToggleVisibility() funkcji.

W APointLight instancje na potrzeby szczebla być ruchomy, dzięki czemu mogą one mieć ich widoczność modyfikowane, jak zobaczymy w przykładzie użytkowania.

Przykładowe użycie:

W tym przykładzie będziesz potrzebować projektu C ++, który ma szablon trzeciej osoby. Dodamy kilku aktorów Point Light do poziomu i przełączymy ich widoczność, naciskając klawisz Enter.

Najpierw utwórzmy mapowanie wejściowe o nazwie PressSwitch, które zostanie uruchomione, gdy gracz naciśnie klawisz Enter.

W edytorze poziomów wejdź do menu Edit -> Project Settings … i w kategorii Engine wybierz opcję Input . Kliknij symbol + obok pozycji Odwzorowania akcji , wprowadź nazwę PressSwitch dla nowego mapowania akcji i wybierz klawisz Enter . Do mapowania akcji dla projektu będzie wyglądać następująco:

Teraz zamierzamy zmodyfikować kodC ++. Otwórz plik nagłówkowy klasy postaci utworzony przez szablon trzeciej osoby . Domyślna nazwa pliku to ProjectNameCharacter.h. Na przykład nazwa mojego projektu to TutoProject, a nazwa pliku nagłówkowego to TutoProjectCharacter.h.

W pliku nagłówkowym dodaj deklarację funkcji ToggleLights() poniżej funkcji SetupPlayerInputComponent():

protected:
  // APawn interface
  virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
                                                                             override;
  // End of APawn interface

  void ToggleLights();

W pliku Character.cpp musisz dodać następujące wiersze #include na początku pliku:

#include "Engine/PointLight.h"
#include "Components/LightComponent.h"
#include "EngineUtils.h"
#include "Engine/World.h"

W funkcji SetupPlayerInputComponent() , Input PressSwitch zostanie powiązany z funkcją ToggleLights() po funkcji Input ResetVR . Zmień nazwę ATutoProjectCharacter na nazwę klasy Character twojego projektu i nie zapomnij umieścić operatora & przed nazwą. Służy do zwracania adresu pamięci funkcji.

PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this,
                                 &ATutoProjectCharacter::OnResetVR);

PlayerInputComponent->BindAction("PressSwitch", IE_Pressed, this,
                                 &ATutoProjectCharacter::ToggleLights);

Na końcu pliku cpp utwórz funkcję ToggleLights() z poniższą zawartością. Nie zapomnij zmienić nazwy klasy, która znajduje się przed operatorem „ :: ”.

void ATutoProjectCharacter::ToggleLights()
{ 
  for (APointLight* PointLightInstance : TActorRange<APointLight>( GetWorld() ) )
  {
    PointLightInstance->GetLightComponent()->ToggleVisibility();
  }	
}

Skompiluj kod C++. Aby przetestować nasz przykład, musimy dodać instancje Point Light na poziomie. Przejdź do zakładki Place ACtctor , kategoria Basic . Przeciągnij kilka Point Light i upuść je na jednej ze ścian poziomu.

Wybierz każdą z instancji Point Light i zmień właściwość na Movable .

Uruchom grę i podejdź do instancji Point Light. Naciśnij klawisz Enter , aby wyłączyć i włączyć światła punktowe.


Źródło:https://romeroblueprints.blogspot.com/2020/12/for-loops-and-tactorrange.html

TArray: Arrays w C++

TArray to typ kontenera, który przechowuje jeden lub więcej elementów tego samego typu. Kontener to typ klasy, której instancje są kolekcjami innych obiektów. Inne przykłady kontenerów w Unreal C++ to TSet i TMap, które zostaną omówione w następnych artykułach.

Prostym sposobem zrozumienia koncepcji Array jest obejrzenie jej w edytorze Unreal. Ten kod tworzy Array o nazwie TownNames do przechowywania elementów typu FString

UPROPERTY(EditAnywhere, Category=ArrayTown)
TArray<FString> TownNames;

Poniższy obraz przedstawia tablicę w edytorze. Możesz dodawać elementy, klikając ikonę +. Liczby po lewej stronie to indeksy elementów tablicy. Pierwszy element tablicy ma indeks zerowy.

Dwie funkcje mogą służyć do dodawania elementów do tablicy przy użyciu C ++. Funkcja Add() dodaje elementy na końcu tablicy, a funkcja Insert() dodaje element w indeksie przekazanym jako parametr.

TownNames.Add(TEXT("Tarrin"));

TownNames.Insert(TEXT("Pitmerden"), 2);

Możesz czytać i modyfikować elementy Array za pomocą operatora [] i indeksu elementu.

FString FirstTown = TownNames[0];

TownNames[3] = TEXT("Penkurth");

Uważaj , jeśli użyjesz indeksu mniejszego niż 0 lub równego lub większego niż liczba elementów w tablicy, spowoduje to błąd w czasie wykonywania.

Aby sprawdzić liczbę elementów w tablicy, użyj funkcji Num():

int32 NumberOfElements = TownNames.Num();

Przykładowe użycie:

Utworzymy tablicę do przechowywania odniesień do instancji Actor, a następnie napiszemy na ekranie nazwy instancji, które zostały dodane do tablicy.

Utwórz klasę C ++ o nazwie TestTArray, używając klasy Actor jako klasy nadrzędnej. W pliku TestTArray.h dodaj deklarację komponentów i TArray, jak pokazano w tym kodzie:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestTArray.generated.h"

UCLASS()
class TUTOPROJECT_API ATestTArray : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATestTArray();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMesh;
	
	UPROPERTY(EditAnywhere, Category=ArrayExample)
	TArray<AActor*> SelectedActors;

};

Oto zawartość pliku  TestTArray.cpp:

#include "TestTArray.h"

ATestTArray::ATestTArray()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMesh->SetupAttachment(RootScene);
}

// Called when the game starts or when spawned
void ATestTArray::BeginPlay()
{
  Super::BeginPlay();

  FString Message;
  
  if(SelectedActors.Num() > 0)
  {
    Message = FString::Printf(TEXT("Number of elements in the array: %d"), 
                              SelectedActors.Num());

    if(GEngine)
    {	
      GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
	  
      Message = FString::Printf(TEXT("--- Selected Actors ---"));
	  
      GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
	  
      for (AActor* ActorInstance : SelectedActors)
      {
        Message = FString::Printf(TEXT("Instance Name: %s"),
                                  *(ActorInstance->GetName()));
		
        GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
      }
    }	  
  }	    
}

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

W funkcji BeginPlay() używamy pętli for opartej na zakresie, aby iterować po elementach tablicy i zapisywać nazwę każdej instancji na ekranie.

Skompiluj kod C++ i dodaj wystąpienie TestTArray na poziomie. Możesz dodawać elementy do tablicy za pomocą zakładki Details instancji.

Uruchom grę i zobacz nazwy instancji Aktora na ekranie:


Źródło: https://romeroblueprints.blogspot.com/2020/12/tarray-arrays-in-c.html

TSet: Sets w C++

TSet to kolejny typ kontenera podobny do TArray . Koncepcyjnie jes to nieuporządkowana lista unikalnych elementów. Tset szablon posiada parametr umożliwiający zduplikowanych elementów, a także posiada funkcję sortowania, ale w tym artykule skupimy się na tradycyjnym używanie odbiornika.

Tset nie korzysta z indeksu. Wartość elementu jest używana jako klucz. Elementy TSet muszą być tego samego typu.

Poniższy przykład tworzy TSet o nazwie CreaturesByRegion do przechowywania elementów typu FString. Ten zestaw TSet można modyfikować w edytorze.

UPROPERTY(EditAnywhere, Category=SetExample)
TSet<FString> CreaturesByRegion;

Użyj funkcji Add (), aby dodać elementy do TSet w C++.

CreaturesByRegion.Add(TEXT("Ghoul"));
CreaturesByRegion.Add(TEXT("Necrophage"));
CreaturesByRegion.Add(TEXT("Werewolf"));

Aby zobaczyć, ile elementów jest w TSet , użyj funkcji Num():

int32 NumberOfElements = CreaturesByRegion.Num();

Użyj funkcji Contains(), aby sprawdzić, czy element jest częścią TSet:

bool bHasCreature = CreaturesByRegion.Contains(TEXT("Necrophage"));

Aby usunąć element, użyj funkcji Remove() . Jeśli element nie zostanie znaleziony, funkcja zwraca wartość 0.

int32 RemovedAmount = CreaturesByRegion.Remove(TEXT("Ghoul"));

Aby usunąć wszystkie elementy z TSet , użyj funkcji Empty () : 

CreaturesByRegion.Empty();

TSet  ma pewne funkcje, które wykonują operacje na dwóch zestawach i zwracają inny zestaw. Oto funkcje:

  • Unio() :  zwraca zestaw TSet zawierający elementy dwóch zestawów. Duplikaty zostaną usunięte.
  • Difference() :  Zwraca TSet, który zawiera elementy pierwszego zestawu, których nie ma w drugim zestawie.
  • Intersect() :  zwraca TSet zawierający tylko elementy, które istnieją w dwóch zestawach.

Poniższy kod przedstawia użycie tych funkcji.

TSet<int32> Set1;
TSet<int32> Set2;

Set1.Add( 3 );
Set1.Add( 5 );
Set1.Add( 7 );

Set2.Add( 4 );
Set2.Add( 5 );
Set2.Add( 6 );

TSet<int32> UnionResult = Set1.Union(Set2);
// UnionResult = [ 3, 4, 5, 6, 7 ] 

TSet<int32> DifferenceResult = Set1.Difference(Set2);
// DifferenceResult = [ 3, 7 ] 

TSet<int32> IntersectResult = Set1.Intersect(Set2);
// IntersectResult = [ 5 ]

Przykładowe użycie:

Utwórz klasę C++ o nazwie TestTSet, używając klasy Actor jako klasy nadrzędnej. W pliku TestTSet.h dodaj deklarację komponentów i TSet:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestTSet.generated.h"

UCLASS()
class TUTOPROJECT_API ATestTSet : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATestTSet();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMesh;
	
	UPROPERTY(EditAnywhere, Category=SetExample)
	TSet<FString> CreaturesByRegion;
};

W funkcji BeginPlay() napiszemy na ekranie nazwy elementów TSet. Wynosiła oparte pętli służy do iteracyjnego Tset elementów. Zawartość pliku TestTSet.cpp wygląda następująco: 

#include "TestTSet.h"

ATestTSet::ATestTSet()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMesh->SetupAttachment(RootScene);
}

// Called when the game starts or when spawned
void ATestTSet::BeginPlay()
{
  Super::BeginPlay();

  FString Message;
  
  if(CreaturesByRegion.Num() > 0)
  {
    Message = FString::Printf(TEXT("Number of elements in the Set: %d"),
                              CreaturesByRegion.Num());

    if(GEngine)
    {		 
      GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
	  
      for (FString Creature : CreaturesByRegion)
      {
        Message = FString::Printf(TEXT("Creature Name: %s"), *Creature);
		
        GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, Message);
      }
    }	  
  }	    
}

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

Skompiluj kod C ++ i dodaj wystąpienie TestTSet na poziomie. Możesz dodawać elementy w TSet, korzystając z zakładki Details instancji. Spróbuj dodać zduplikowaną wartość. Pojawi się komunikat, że jest to niedozwolone.

Uruchom grę i zobacz na ekranie nazwy elementów dodanych do TSet:


Źródło: https://romeroblueprints.blogspot.com/2020/12/tset-sets-in-c.html