Antes de começar
Certifique-se de ter concluído os seguintes objetivos da seção anterior, Gerencie itens e dados:
Configure uma stuct de dados de item, classe
UDataAsset, uma instância de ativo de dados do tipo consumível denominadaDA_Pickup_001e uma tabela de dados.
Crie uma nova classe de coleta
Até agora, você aprendeu a definir e armazenar a estrutura e os dados de um item. Nesta seção, você aprenderá a transformar esses dados em uma "coleta" no jogo, ou seja, uma representação concreta dos dados da tabela com os quais o jogador pode interagir e obter um efeito. Uma coleta pode ser um dispositivo equipável, uma caixa que fornece materiais ou um potencializadores que fornecem um impulso temporário.
Para começar a configurar uma classe de coleta com declarações iniciais, siga estas etapas:
No Unreal Editor, acesse Ferramentas, Nova classe C++. Selecione Ator como a classe pai e dê à classe o nome de
PickupBase. Clique em Criar classe.No Visual Studio, abra
PickupBase.he adicione as seguintes declarações na parte superior do arquivo:#include "Components/SphereComponent.h". Você adicionará um componente de esfera à coleta para detectar colisões entre o jogador e a coleta.#include "AdventureCharacter.h". Adicione uma referência à classe do personagem em primeira pessoa para verificar se há sobreposições entre a coleta e os personagens dessa classe. (Este tutorial usaAdventureCharacter.)Uma declaração de encaminhamento para
UItemDefinition. Este é o item do ativo de dados associado que cada coleta referencia.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Components/SphereComponent.h" #include "CoreMinimal.h" #include "AdventureCharacter.h" #include "GameFramework/Actor.h" #include "PickupBase.generated.h"Na macro
UCLASS()acima da definição da classeAPickupBase, adicione os especificadoresBlueprintTypeeBlueprintablepara expô-la como uma classe base para a criação de Blueprints.C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API APickupBase : public AActor {Em
PickupBase.cpp, adicione um#includeparaItemDefinition.h.C++// Copyright Epic Games, Inc. All Rights Reserved. #include "PickupBase.h" #include "ItemDefinition.h"
Inicialize a coleta com dados da tabela
A coleta é apenas um ator em branco, portanto, quando o jogo começar, você precisará fornecer os dados necessários para o funcionamento correto. A coleta deve extrair uma linha de valores da tabela de dados e salvá-los em um recurso de dados de ItemDefinition (o "item de referência").
Extraia dados de uma tabela de dados
Em PickupBase.h, na seção pública, declare uma nova função void InitializePickup(). Você usará essa função para inicializar a coleta com valores da tabela de dados.
// Initializes this pickup with values from the data table.
void InitializePickup();Para extrair dados da tabela, o Blueprint da coleta precisa de duas propriedades: o ativo de dados da tabela e o nome da linha (que você configurou para que fosse igual ao ID do item).
Na seção protegida, declare uma propriedade FName denominada PickupItemID. Adicione os especificadores EditInstanceOnly e Category = "Pickup | Item Table". Esse é o ID da coleta na tabela de dados associada.
// The ID of this pickup in the associated data table.
UPROPERTY(EditInstanceOnly, Category = "Pickup | Item Table")
FName PickupItemID;As coletas não devem ter um ID de item padrão, por isso, o especificador EditInstanceOnly permite editar essa propriedade em instâncias de coletas no mundo, mas não no arquétipo (ou padrão da classe).
No texto Category, a barra vertical (|) cria uma subseção aninhada. Neste exemplo, a Unreal Engine cria uma seção Pickup com uma subseção chamada Item Table no painel Detalhes do ativo.
Em seguida, declare um TSoftObjectPtr para um UDataTable chamado PickupDataTable. Dê a esses elementos os mesmos especificadores de PickupItemID. Essa é a tabela de dados que a coleta usa para obter os dados.
A tabela de dados pode não ser carregada no tempo de execução, portanto, use um TSoftObjectPtr aqui para poder carregá-la de forma assíncrona.
Salve o arquivo de cabeçalho e mude para PickupBase.cpp para implementar InitializePickup().
Dentro da função, em uma declaração "if", verifique se a PickupDataTable fornecida é válida e se PickupItemID tem um valor.
/**
* Initializes the pickup with default values by retrieving them from the associated data table.
*/
void APickupBase::InitializePickup()
{
if (PickupDataTable && !PickupItemID.IsNone())
{
}
}Na instrução if, adicione o código para recuperar a linha de valores da Tabela de Dados. Declare um ponteiro FItemData de constante chamado ItemDataRow e defina-o como o resultado da chamada de FindRow() em PickupDataTable. Especifique FItemData como o tipo de linha a ser encontrada.
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>();FindRow() recebe dois argumentos:
Um nome de linha
FNameque você deseja encontrar. PassePickupItemIDcomo o nome da linha.Uma string de contexto do tipo
FStringque você pode usar para a depuração se a linha não for encontrada. Você pode incluirText("My context here.")para adicionar uma string de contexto ou useToString()para converter o ID do item em uma string de contexto.
if (PickupDataTable && !PickupItemID.IsNone())
{
// Retrieve the item data associated with this pickup from the Data Table
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString());
}Crie um item de referência
Depois de recuperar os dados de linha da coleta, crie e inicialize um ReferenceItem do tipo ativo de dados para armazenar essas informações.
Ao salvar os dados em um item de referência como esse, a Unreal Engine pode referenciar facilmente esses dados quando precisar saber sobre o item, em vez de realizar mais pesquisas de dados da tabela, o que é menos eficiente.
Em PickupBase.h, na seção protegida, declare um TObjectPtr para uma UItemDefinition denominada ReferenceItem. Esse é um ativo de dados que armazena os dados de coleta. Adicione VisibleAnywhere e Category = "Pickup | Reference Item".
// Data asset associated with this item.
UPROPERTY(VisibleAnywhere, Category = "Pickup | Reference Item")
TObjectPtr<UItemDefinition> ReferenceItem;Salve o arquivo de cabeçalho e volte para PickupBase.cpp.
Em InitializePickup(), após a chamada de FindRow(), defina ReferenceItem como um NewObject do tipo UItemDefinition.
Na Unreal Engine, NewObject<T>() é uma função de modelo para criar dinamicamente instâncias derivadas de UObject no tempo de execução. Ela retorna um ponteiro para o novo objeto. Ele costuma ter a seguinte sintaxe:
T* Object = NewObject<T>(Outer, Class);
Onde T é o tipo de UObject que você está criando, Outer é o proprietário deste objeto e Class é a classe do objeto que você está criando. O argumento Class costuma ser T::StaticClass(), que fornece um ponteiro UClass que representa o tipo de classe T. Porém, você pode omitir os dois argumentos, pois a UE presume que Outer é a classe atual e usa T para inferir a UClass.
Passe isso como a classe externa e UItemDefinition::StaticClass() como o tipo de classe para criar uma UItemDefinition base.
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());Para copiar as informações da coleta em ReferenceItem, defina cada campo em ReferenceItem como o campo associado em ItemDataRow. Para WorldMesh, extraia a propriedade WorldMesh de ItemBase referenciada em ItemDataRow.
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());
ReferenceItem->ID = ItemDataRow->ID;
ReferenceItem->ItemType = ItemDataRow->ItemType;
ReferenceItem->ItemText = ItemDataRow->ItemText;
ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;Chame InitializePickup()
Em BeginPlay(), chame InitializePickup() para inicializar a coleta quando o jogo começar.
// Called when the game starts or when spawned
void APickupBase::BeginPlay()
{
Super::BeginPlay();
// Initialize this pickup with default values
InitializePickup();
}Salve o arquivo. PickupBase.cpp deve estar assim:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
}
Crie funcionalidade no jogo
A coleta tem os dados do item necessários, mas ela ainda precisa saber como aparecer e operar no mundo do jogo. Ela precisa de uma malha para o jogador ver, um volume de colisão para determinar quando o jogador toca nela e alguma lógica para a fazer desaparecer (como se o jogador o tivesse a coletado) e ressurgir após um determinado período de tempo.
Adicione um componente de malha
Assim como fez ao configurar o personagem jogador em Como adicionar câmera, malha e animação em primeira pessoa, você usará a função modelo CreateDefaultSubobject para criar um objeto de malha estática como um componente filho da classe da coleta e aplicará a malha do item a este componente.
Em PickupBase.h, na seção protegida, declare um TObjectPtr para um UStaticMeshComponent chamado PickupMeshComponent. Essa é a malha que representará a coleta no mundo.
Você usará um código para atribuir a malha do ativo de dados a essa propriedade, portanto, insira os especificadores VisibleDefaultsOnly e Category = "Pickup | Mesh" para que fique visível, mas não editável, no Unreal Editor.
// The mesh component to represent this pickup in the world.
UPROPERTY(VisibleDefaultsOnly, Category = "Pickup | Mesh")
TObjectPtr<UStaticMeshComponent> PickupMeshComponent;Salve o arquivo de cabeçalho e mude para PickupBase.cpp.
Na função de construção APickupBase, defina o ponteiro PickupMeshComponent como o resultado da chamada de CreateDefaultSubobject() do tipo UStaticMeshComponent. No argumento Text, dê ao objeto o nome de "PickupMesh".
Para garantir que a malha foi instanciada corretamente, verifique se PickupMeshComponent não é nulo.
// Sets default values
APickupBase::APickupBase()
{
// 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;
// Create this pickup's mesh component
PickupMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PickupMesh"));
check(PickupMeshComponent != nullptr);
}Vá para a implementação de InitialisePickup().
Antes de aplicar WorldMesh ao componente de malha da coleta, você precisará verificar se a malha está carregada, pois você a definiu com um TSoftObjectPtr.
Primeiro, declare um novo ponteiro UItemDefinition chamado TempItemDefinition e defina-o como o resultado da chamada ItemDataRow->ItemBase.Get().
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();Em seguida, em uma declaração if, verifique se o elemento WorldMesh está carregado no momento chamando WorldMesh.IsValid().
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
}Se for, defina PickupMeshComponent chamando SetStaticMesh() e recupere WorldMesh usando Get():
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
// Set the pickup's mesh to the associated item's mesh
PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());
}Se a malha não estiver carregada, force o carregamento chamando LoadSynchronous() na malha. Essa função carrega e retorna um ponteiro de ativo para esse objeto.
Após a declaração if, em uma declaração else, declare um novo ponteiro UStaticMesh chamado WorldMesh e defina-o chamando WorldMesh.LoadSynchronous().
Em seguida, defina PickupMeshComponent usando SetStaticMesh().
else {
// If the mesh isn't loaded, load it by calling LoadSynchronous().
UStaticMesh* WorldMesh = TempItemDefinition->WorldMesh.LoadSynchronous();
PickupMeshComponent->SetStaticMesh(WorldMesh);
}Após a declaração else, torne PickupMeshComponent visível usando SetVisiblity(true):
// Set the mesh to visible.
PickupMeshComponent->SetVisibility(true);Adicione uma forma de colisão
Adicione um componente de esfera para atuar como um volume de colisão e habilite consultas de colisão nesse componente.
Em PickupBase.h" na seção protegida, declare um TObjectPtr para um USphereComponent chamado SphereComponent. Esse é o componente de esfera usado para a detecção de colisão. Atribua os especificadores EditAnywhere, BlueprintReadOnly e Category = "Pickup | Components".
// Sphere Component that defines the collision radius of this pickup for interaction purposes.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Components")
TObjectPtr<USphereComponent> SphereComponent;Salve o arquivo de cabeçalho e mude para PickupBase.cpp.
Na função de construção APickupBase, depois de definir PickupMeshComponent, defina SphereComponent como o resultado de chamar CreateDefaultSubobject com USphereComponent como o tipo e SphereComponent como o nome. Adicione uma verificação de nulo depois.
// Create this pickup's sphere component
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
check(SphereComponent != nullptr);Agora que você tem os dois componentes, use SetupAttachment() para anexar PickupMeshComponent a SphereComponent:
// Attach the sphere component to the mesh component
SphereComponent->SetupAttachment(PickupMeshComponent);Após anexar SphereComponent a MeshComponent, defina o tamanho da esfera usando SetSphereRadius(). Esse valor deve deixar o colisor de coleta grande o suficiente para colidir, mas não tão grande a ponto de o personagem esbarrar nele por acidente.
// Set the sphere's collision radius
SphereComponent->SetSphereRadius(32.f);Em InitializePickup(), após a linha SetVisibility(true), torne SphereComponent colidível chamando SetCollisionEnabled().
Essa função usa uma enumeração (ECollisionEnabled) que informa à engine o tipo de colisão a ser usada. O objetivo é permitir que o personagem colida e acione consultas de colisão com a coleta, mas a coleta não deve ter nenhuma física que a faça rebater ao ser atingida; portanto, passe a opção ECollisionEnabled::QueryOnly.
// Set the mesh to visible and collidable.
PickupMeshComponent->SetVisibility(true);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly); PickupBase.cpp agora deve ficar assim:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// 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;
Simule colisões da coleta
Agora que a coleta tem um formato de colisão, adicione uma lógica para detectar uma colisão entre a coleta e o jogador e fazer a coleta desaparecer ao colidir com ela.
Configure o evento de colisão
Em PickupBase.h, na seção protected, declare uma função void chamada OnSphereBeginOverlap().
Qualquer componente que herda de UPrimitiveComponent, como USphereComponent, pode implementar esta função para executar código quando o componente se sobrepõe a algum outro Ator. Esta função aceita vários parâmetros que não serão usados; você só transmitirá os seguintes:
UPrimitiveComponent* OverlappedComponent: o componente que foi sobreposto.AActor* OtherActor: o ator que se sobrepõe a esse componente.UPrimitiveComponent* OtherComp: o componente do ator que se sobrepôs.int32 OtherBodyIndex: o índice do componente sobreposto.bool bFromSweep, const FHitResult& SweepResult: informações sobre a colisão, como onde ocorreu e em que ângulo.
// Code for when something overlaps the SphereComponent.
UFUNCTION()
void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);Salve o arquivo de cabeçalho e mude para PickupBase.cpp.
Os eventos de colisão da Unreal Engine são implementados usando delegados de multicasting dinâmicos. Na UE, esse sistema de delegados permite que um objeto execute várias chamadas de função ao mesmo tempo, como se estivesse transmitindo uma mensagem a uma lista de e-mails cujos assinantes são essas outras funções. Quando vinculamos funções ao delegado, é como se o inscrevêssemos na lista de e-mails. O "delegado" é o nosso evento; neste caso, uma colisão entre o jogador e a coleta. Quando o evento acontece, a Unreal Engine chama todas as funções vinculadas a esse evento.
O Unreal Engine inclui algumas outras funções de vinculação, mas você vai querer usar AddDynamic() porque seu delegado, OnComponentBeginOverlap, é um delegado dinâmico. E você está vinculando uma UFUNCTION em uma classe UObject, exigindo AddDynamic() para suporte de reflexo. Para obter mais informações sobre delegados multicast dinâmicos, consulte Multi-cast Delegates.
Em PickupBase.cpp, em InitializePickup(), use a macro AddDynamic para vincular OnSphereBeginOverlap() ao evento OnComponentBeginOverlap do componente de esfera.
// Register the Overlap Event
SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &APickupBase::OnSphereBeginOverlap);Salve o trabalho. Agora, OnSphereBeginOverlap() é executado quando um personagem colide com o componente de esfera da coleta.
Oculte a coleta após uma colisão
Em PickupBase.cpp, implemente OnSphereBeginOverlap() para fazer seu item de coleta desaparecer, de modo que pareça que o jogador o pegou.
Comece adicionando uma mensagem de depuração para sinalizar quando essa função for acionada.
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Attempting a pickup collision"));
}Como essa função é executada sempre que outro ator colide com a coleta, verifique se é o personagem em primeira pessoa que está colidindo.
Declare um novo ponteiro AAdventureCharacter denominado Personagem e defina-o convertendo OtherActor para o nome da sua classe Personagem (este tutorial usa AAdventureCharacter).
// Checking if it's an AdventureCharacter overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);Em uma instrução if, verifique se Personagem não é nulo. Nulo indica que a conversão falhou e que OtherActor não era algum tipo de AAdventureCharacter.
Dentro da instrução if, cancele o registro de OnComponentBeginOverlap desta função chamando RemoveAll() para que ela não seja acionada repetidamente. Isso encerra a colisão.
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
}Em seguida, defina o PickupMeshComponent como invisível usando SetVisibility(false) e defina a malha de coleta e o componente de esfera como não colidíveis usando SetCollisionEnabled(), transmitindo a opção NoCollision.
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
// Set this pickup to be invisible and disable collision
PickupMeshComponent->SetVisibility(false);
PickupMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}Salve PickupBase.cpp.
Faça a coleta ressurgir
Como o personagem não pode interagir com a coleta, faça com que ele ressurja após um período determinado.
Em PickupBase.h, na seção protegida, declare um bool denominado bShouldRespawn. Você pode usar isso para ativar ou desativar os ressurgimentos.
Declare um float chamado RespawnTime inicializado como 4.0f. Esse é o tempo de espera até que a coleta ressurja.
Dê a ambas as propriedades os especificadores EditAnywhere, BlueprintReadOnly e Category = "Pickup | Respawn".
// Whether this pickup should respawn after being picked up.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
bool bShouldRespawn;
// The time in seconds to wait before respawning this pickup.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
float RespawnTime = 4.0f;Declare um FTimerHandle denominado RespawnTimerHandle.
// Timer handle to distinguish the respawn timer.
FTimerHandle RespawnTimerHandle;No Unreal Engine, os temporizadores de jogabilidade são controlados pelo FTimerManager. Esta classe inclui a função SetTimer(), que chama uma função ou delegado após um atraso definido. As funções do FTimerManager usam um FTimerHandle para iniciar, pausar, retomar ou executar um loop infinito da função. Você usará RespawnTimerHandle para sinalizar quando reaparecer a coleta. Para saber mais sobre como usar o Gerenciador de temporizador, consulte Temporizadores de jogabilidade.
Salve o arquivo de cabeçalho e mude para PickupBase.cpp.
Para implementar o ressurgimento de coleta, use o Gerenciador de temporizador para definir um temporizador que chama InitializePickup() após uma curta espera.
Você só quer fazer o com que a coleta ressurja se os ressurgimentos estiverem habilitados; então, na parte inferior de OnSphereBeginOverlap, adicione uma instrução if que verifica se bShouldRespawn é verdadeiro.
if (bShouldRespawn)
{
}Na instrução if, obtenha o Gerenciador de temporizador usando GetWorldTimerManager() e chame SetTimer() no Gerenciador de temporizador. Essa função tem a seguinte sintaxe:
SetTimer(InOutHandle, Object, InFuncName, InRate, bLoop, InFirstDelay);
Onde:
InOutHandleé oFTimerHandleque controla o temporizador (seuRespawnTimerHandle).Objetoé oUObjectque possui a função que você está chamando. Use isso.InFuncNameé um ponteiro para a função que você deseja chamar (InitializePickup()neste caso).InRateé um valor float que especifica o tempo em segundos de espera antes de chamar sua função.bLoopfaz o temporizador repetir a cadaTemposegundos (verdadeiro) ou disparar apenas uma vez (falso).InFirstDelay(opcional) é um atraso de tempo inicial antes da primeira chamada de função em um temporizador de loop. Se não for especificado, o UE usaInRatecomo atraso.
Você só quer chamar InitializePickup() uma vez para substituir a coleta, então defina bLoop como falso.
Defina o tempo de ressurgimento de sua preferência; este tutorial faz a coleta ressurgir após quatro segundos sem atraso inicial.
// If the pickup should respawn, wait an fRespawnTime amount of seconds before calling InitializePickup() to respawn it
if (bShouldRespawn)
{
GetWorldTimerManager().SetTimer(RespawnTimerHandle, this, &APickupBase::InitializePickup, RespawnTime, false, 0);
}Sua função OnSphereBeginOverlap() completa deve ficar assim:
/**
* Broadcasts an event when a character overlaps this pickup's SphereComponent. Sets the pickup to invisible and uninteractable, and respawns it after a set time.
* @param OverlappedComponent - the component that was overlapped.
* @param OtherActor - the Actor overlapping this component.
* @param OtherComp - the Actor's component that overlapped this component.
* @param OtherBodyIndex - the index of the overlapped component.
* @param bFromSweep - whether the overlap was generated from a sweep.
* @param SweepResult - contains info about the overlap such as surface normals and faces.
*/
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
Salve o código e compile pelo Visual Studio.
Implemente coletas no nível
Agora que você definiu o código que compõe as coletas, é hora de testá-las no jogo.
Para adicionar coletas ao nível, siga estas etapas:
No Unreal Editor, na árvore de ativos do Navegador de Conteúdo, acesse Conteúdo > FirstPerson > Blueprints.
Na pasta Blueprints, crie uma nova pasta filha denominada Coletar para armazenar suas classes de coleta.
Na árvore de ativos, acesse a pasta Classes C++. Clique com o botão direito do mouse na sua classe PickupBase para criar um Blueprint a partir dessa classe.
Nomeie-a como
BP_PickupBase.Para o Caminho, selecione Conteúdo/FirstPerson/Blueprints/Coletas e clique em Criar classe case de coleta.
Volte para a pasta Blueprints > Coletas. Arraste seu
Blueprint BP_PickupBasepara o nível. Uma instância do PickupBase aparece no seu nível e é selecionada automaticamente no painel Outliner. No entanto, ele ainda não tem uma malha.Com o ator
BP_PickupBaseainda selecionado, no painel Detalhes, defina as seguintes propriedades:Defina o ID do item de coleta como
pickup_001.Defina a Tabela de dados de coleta como
DT_PickupData.Defina Deve ressurgir como verdadeiro.
Quando você clica em Jogar para testar seu jogo, sua coleta usa o ID do item de coleta para consultar a Tabela de dados e recuperar dados associados a pickup_001. A coleta usa dados da tabela e a referência ao seu ativo de dados DA_Pickup_001 para inicializar um item de referência e carregar sua malha estática.
Ao passar por cima da coleta, você deverá vê-la desaparecer e reaparecer quatro segundos depois.
Carregar uma Coleta Diferente
Se você definir o ID do item de coleta como um valor diferente, sua coleta recuperará dados dessa linha na tabela.
Para experimentar a troca do ID do item de coleta, siga estas etapas:
Crie um novo ativo de dados denominado DA_Pickup_002. Defina as seguintes propriedades neste ativo:
ID: pickup_002
Tipo de item: consumível
Nome: nome do teste 2
Descrição: descrição do teste 2
Malha do mundo:
SM_ChamferCube
Adicione uma nova linha na tabela
DT_PickupDatae insira as informações do Ativo de Dados nos campos da nova linha.No ator
BP_PickupBase, altere o ID do item de coleta parapickup_002.
Quando você clica em Jogar para testar seu jogo, a coleta deve aparecer com os valores de DA_Pickup_002!
Atualize os atores de coleta no editor
As coletas funcionam no jogo, mas pode ser difícil visualizá-las no editor, pois elas não têm uma malha padrão.
Para corrigir isso, use a função PostEditChangeProperty(). Esta é uma função no editor que o Unreal Engine chama quando o editor altera uma propriedade, para que você possa usá-la para manter seus atores atualizados na Janela de Visualização conforme suas propriedades mudam. Por exemplo, atualizar um elemento da interface de usuário ao alterar a vida padrão de um jogador ou dimensionamento uma esfera ao aproximá-la ou afastá-la da origem.
Neste projeto, você a usará para aplicar a nova malha estática da coleta sempre que o ID do item de coleta for alterado. Dessa forma, você pode alterar o tipo de coleta e vê-lo atualizar imediatamente na janela de visualização sem precisar clicar em "Jogar".
Para que as alterações nas coletas apareçam imediatamente no editor, siga estas etapas:
Em
PickupBase.h, na seçãoprotegida, declare uma macro#if WITH_EDITOR. Esta macro informa ao Unreal Header Tool que qualquer coisa dentro dela deve ser empacotada apenas para compilações do editor e não compilada para versões de lançamento do jogo. Finalize esta macro com uma instrução#endif.C++#if WITH_EDITOR #endifDentro da macro, declare uma substituição de função void virtual para
PostEditChangeProperty(). Esta função recebe uma referência aoFPropertyChangedEvent, que inclui informações sobre a propriedade alterada, o tipo de alteração e muito mais. Salve o arquivo de cabeçalho.C++#if WITH_EDITOR // Runs whenever a property on this object is changed in the editor virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endifEm
PickupBase.cpp, implemente a funçãoPostEditChangeProperty(). Comece chamando a funçãoSuper::PostEditChangeProperty()para manipular quaisquer alterações na propriedade da classe pai.C++/** * Updates this pickup whenever a property is changed. * @param PropertyChangedEvent - contains info about the property that was changed. */ void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { // Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); }Crie uma nova variável
const FNamedenominadaChangedPropertypara armazenar o nome da propriedade alterada.C++// Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); const FName ChangedPropertyName;Para verificar se o
PropertyChangedEventinclui umaPropriedadee salvar essa propriedade, use um operador ternário comPropertyChangedEvent.Propertycomo condição. DefinaChangedPropertyNamecomoPropertyChangedEvent.Property->GetFName()se a condição for verdadeira e defina-a comoNAME_Nonese for falsa.C++// If a property was changed, get the name of the changed property. Otherwise use none. const FName ChangedPropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;NAME_Noneé uma constante global do Unreal Engine do tipoFNameque significa "nenhum nome válido" ou "nome nulo".Agora que você sabe o nome da propriedade, pode fazer com que o Unreal Engine atualize a malha se ele detectar que o ID foi alterado.
Em uma instrução
if, verifique seChangePropertyNameé igual ao resultado da chamada deGET_MEMBER_NAME_CHECKED(), transmitindo esta classeAPickupBasee oPickupItemID. Essa macro faz uma verificação em tempo de compilação para garantir que a propriedade passada existe na classe passada.Você também recuperará valores da Tabela de Dados, portanto, verifique se a tabela é válida antes de inserir a instrução
if.C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { }Dentro da instrução
if, recupere a linha de dados associada a esta coleta da mesma forma que você fez emInitializePickup()chamandoFindRow.Desta vez, para garantir que o
PickupItemIDesteja na tabela antes de continuar, coloque a linhaFindRowem uma instruçãoifaninhada.C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { // Retrieve the associated ItemData for this pickup. if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { } }Se o UE encontrar os dados da linha com sucesso, crie uma variável
TempItemDefinitionpara armazenar o Ativo de Dados (que contém a nova malha) referenciado noItemDataRow.C++if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase;Para atualizar a malha, use
SetStaticMeshnoPickupMeshComponent, transmitindo oWorldMeshdo ativo de dados temporário.C++// Set the pickup's mesh to the associated item's mesh PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());Defina o raio de colisão do componente Esfera usando
SetSphereRadius(32.f).C++// Set the sphere's collision radius SphereComponent->SetSphereRadius(32.f);Salve o código e compile pelo Visual Studio.
Sua função PostEditChangeProperty() completa deve ficar assim:
/**
* Updates this pickup whenever a property is changed.
* @param PropertyChangedEvent - contains info about the property that was changed.
*/
void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Handle parent class property changes
Super::PostEditChangeProperty(PropertyChangedEvent);
// If a property was changed, get the name of the changed property. Otherwise use none.
De volta ao editor, no Outliner, certifique-se de que seu ator BP_PickupBase esteja selecionado. Altere o ID do item de coleta de pickup_001 para pickup_002 e depois altere-o novamente. À medida que você altera o ID, a malha da sua coleta é atualizada na Janela de Visualização.
Se estiver experimentando outras malhas, talvez seja necessário jogar o jogo uma vez para forçar o carregamento completo de uma nova malha antes de poder vê-la na Janela de Visualização do editor.
Próxima
Em seguida, você ampliará sua classe de coleta para criar um dispositivo personalizar e equipá-lo com o personagem.
Equipar seu Personagem
Aprenda a usar C++ para criar itens equipáveis personalizados e anexá-los ao seu personagem.
Código completo
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/SphereComponent.h"
#include "CoreMinimal.h"
#include "AdventureCharacter.h"
#include "GameFramework/Actor.h"
#include "PickupBase.generated.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// 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;