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.