Primeiro Projeto Completo em Unreal Engine: Lições Aprendidas

Delegados C++ e Integração com Blueprint

Para expor eventos do C++ ao Blueprint, é essencial usar delegados multicast dinâmicos. Eles permitem que Blueprints escutem mudanças ou ações disparadas por classes nativas.


// Declaração do delegado
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDataRefresh);

// Uso na classe
UCLASS()
class AGameStateManager : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(BlueprintAssignable)
    FOnDataRefresh OnDataUpdated;
};

Chamando Métodos C++ a Partir do Blueprint

Utilize BlueprintCallable para permitir que funções C++ sejam invocadas diretamente em Blueprints.


UFUNCTION(BlueprintCallable, Category = "UI")
FString GetDisplayName() const;

Gerenciamento de Ativos de Dados (Primary Data Assets)

Quando se trabalha com UPrimaryDataAsset, evite gerar arquivos CPP automaticamente. O sistema de ativos pode falhar ao carregar o USTRUCT referenciado se houver um arquivo CPP vazio. Remover o .cpp e recompilar resolve esse problema.


USTRUCT(BlueprintType)
struct FStatusEntry
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 StatusID;
};

UCLASS()
class UStatusDatabase : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TArray<FStatusEntry> StatusList;
};

Trabalhando com Tabelas de Dados (DataTables)

Estruturas usadas em DataTables devem herdar de FTableRowBase. O carregamento assíncrono permite acessso direto aos dados via chave.


USTRUCT(BlueprintType)
struct FUnitInfo : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 UnitIdentifier;
};

// Carregar e iterar
UDataTable* Table = LoadObject<UDataTable>(nullptr, TEXT("DataTable'/Game/Data/Units.UnitTable'"));
if (Table)
{
    TArray<FUnitInfo*> Rows;
    Table->GetAllRows(nullptr, Rows);
}

Manipulação Dinâmica de Componentes

Componentes podem ser criados e anexados dinamicamente no início do jogo. Embora Blueprints restrinjam a remoção de componentes, isso é possível via C++.


// Adicionar componente
UInitComponent* InitComp = NewObject<UInitComponent>(this, TEXT("Initializer"));
AddInstanceComponent(InitComp);
InitComp->RegisterComponent();

// Anexar ao Root
InitComp->AttachTo(RootComponent, &FAttachmentTransformRules::KeepRelativeTransform);

// Destruir componente (somente em C++)
InitComp->DestroyComponent();

Obtendo Componantes e Atores

Use métodos fortemente tipados para recuperar componentes ou atores específicos.


// Obter componente
USplineComponent* Spline = FindComponentByClass<USplineComponent>();

// Obter ator pela classe
ACharacter* Player = Cast<ACharacter>(UGameplayStatics::GetActorOfClass(GetWorld(), ACharacter::StaticClass()));

Criação Dinâmica de Atores a Partir de Blueprints

Carregue dinamicamente uma classe de Blueprint e instancie-a no mundo atual.


FString AssetPath = TEXT("Blueprint'/Game/Blueprints/BP_Enemy.BP_Enemy_C'");
UClass* EnemyClass = LoadClass<AActor>(nullptr, *AssetPath);
if (EnemyClass)
{
    GetWorld()->SpawnActor(EnemyClass);
}

Comparação de Strings

O operador == em FString já lida com comparação sensível/descendente a maiúsculas. Internamente, utiliza funções otimizadas como FCString::Strcmp.


if (MyString == TEXT("Target"))
{
    // Executa ação
}

Execução com Atraso e Repetição com TimerManager

Use FTimerHandle para agendar chamadas de função com ou sem repetição.


FTimerHandle Timer;
GetWorld()->GetTimerManager().SetTimer(Timer, this, &AGameController::OnPhaseChange, 3.0f, false); // Uma vez

// Cancelar temporizador
GetWorld()->GetTimerManager().ClearTimer(Timer);

Interfaces C++ Similares ao COM (Exemplo Inspirado no DirectX)

Embora não seja comum no Unreal, interfaces estilo COM podem ser implementadas usando herança pura e métodos abstratos.


class IShaderInclude
{
public:
    virtual HRESULT Open(D3D_INCLUDE_TYPE Type, LPCSTR Filename, const void* ParentData, LPCVOID* Data, UINT* Bytes) = 0;
    virtual HRESULT Close(LPCVOID Data) = 0;
    virtual ~IShaderInclude() = default;
};

class FCustomShaderInclude : public IShaderInclude
{
    // Implementação personalizada
};

Animação Baseada em Spline (Caminho 3D)

Use USplineComponent para movimentar objetos ao longo de trajetórias definidas visualmente no editor.


void ARouteMover::GetPositionAndRotation(float Distance, FVector& OutLocation, FRotator& OutRotation, bool bReverse)
{
    float EvalDistance = bReverse ? TotalLength - Distance : Distance;
    OutLocation = Spline->GetLocationAtDistanceAlongSpline(EvalDistance, ESplineCoordinateSpace::World);
    OutRotation = Spline->GetRotationAtDistanceAlongSpline(EvalDistance, ESplineCoordinateSpace::World);
}

Detecção por Raio (Line Trace)

Utilize LineTraceSingleByChannel para detectar colisões com geometria estática ou outros atores.


FHitResult Hit;
FVector Start = GetActorLocation();
FVector End = Start + GetActorUpVector() * -1000.0f;

bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility);
if (bHit)
{
    UE_LOG(LogTemp, Warning, TEXT("Colidiu com: %s"), *Hit.Actor->GetName());
}

Funções de Ajuda para Instanciação de Ativos

Crie funções utilitárias para carregar e spawnar atores de forma genérica.


AActor* SpawnActorFromAsset(UObject* Context, const FString& Path)
{
    UClass* LoadedClass = LoadClass<AActor>(nullptr, *Path);
    if (LoadedClass && Context && Context->GetWorld())
    {
        return Context->GetWorld()->SpawnActor(LoadedClass);
    }
    return nullptr;
}

UI com Listas e Itens Reutilizáveis

Para listas roláveis com muitos itens, implemente UUserWidget que herde de UUserListEntry. Isso garante suporte à reutilização e vinculação correta de dados.

Problema comum: ao rolar, os dados visuais dos itens anteriores são sobrescritos. Solução: mantenha o estado dos dados modificados fora da exibição direta, sincronizando com uma fonte de verdade persistente.

Conclusão: UE vs Unity

A curva de aprendizado entre Unity e Unreal é comparável para desenvolvedores experientes. Blueprints facilitam protótipos rápidos, mas o controle total exige C++. Para proejtos complexos, o uso de C++ oferece melhor desempenho, depuração e manutenção.

Tags: Unreal Engine C++ Blueprints DataTables PrimaryDataAsset

Publicado em 8-20 03:05