Olá pessoal! Meu nome é Alexander, trabalho com a Unreal Engine há mais de 5 anos e quase todo esse tempo - em projetos de rede.
Como os projetos de rede diferem em seus requisitos de desenvolvimento e desempenho, geralmente é necessário trabalhar com objetos mais simples, como as classes UObject, mas sua funcionalidade é inicialmente truncada, o que pode criar uma estrutura sólida. Neste artigo, falarei sobre como ativar várias funções na classe base UObject no Unreal Engine 4.

De fato, escrevi o artigo mais como referência. A maioria das informações é extremamente difícil de encontrar na documentação ou na comunidade, e aqui você pode abrir rapidamente o link e copiar o código desejado. Decidi ao mesmo tempo compartilhar com você! O artigo é direcionado para aqueles que já estão um pouco familiarizados com o UE4. O código C ++ será considerado, embora não seja necessário conhecê-lo. Você pode simplesmente seguir as instruções se precisar de algo para conversar. Além disso, não é necessário copiar tudo, você pode colar o código da seção com as propriedades necessárias e deve funcionar.
Um pouco sobre o UObject
UObject é a classe base de quase tudo o que existe no Unreal Engine 4. A grande maioria dos objetos criados em seu mundo ou apenas na memória é herdada dele: objetos no palco (AActor), componentes (UActorComponent), tipos diferentes para trabalhar com dados e outros.
A classe em si, embora mais fácil do que derivadas, é ao mesmo tempo bastante funcional. Por exemplo, ele contém muitos eventos úteis, como alterar os valores de variáveis no editor e funções básicas da rede, que não estão ativas por padrão.
Os objetos criados por esta classe não podem estar no palco e existir exclusivamente na memória. Eles não podem ser adicionados como componentes aos atores, embora possa ser um tipo de componente se você implementar a funcionalidade necessária.
Por que preciso do UObject se o AActor já suporta tudo o que preciso? Em geral, existem muitos exemplos de uso. O mais fácil são os itens de inventário. No palco, em algum lugar do céu, é impraticável armazená-los, para que você possa armazená-los na memória sem carregar a renderização e sem criar propriedades desnecessárias. Para quem gosta de comparações técnicas, o AActor ocupa um kilobyte (1016 bytes) e um UObject vazio tem apenas 56 bytes.
O que é um problema do UObject?
Em geral, não há problemas, ou simplesmente não os encontrei. Tudo o que incomoda o UObject é a falta de vários recursos disponíveis por padrão no AActor ou nos componentes. Aqui estão os problemas que eu identifiquei para a minha prática:
- UObjects não são replicados pela rede;
- por causa do primeiro ponto, não podemos disparar eventos RPC;
- Você não pode usar um conjunto extenso de funções que requerem um link para o mundo no Blueprints;
- eles não têm eventos padrão como BeginPlay e Tick;
- você não pode adicionar componentes do UObjects ao AActor no Blueprints.
A maioria das coisas pode ser facilmente resolvida. Mas alguns terão que mexer.
Criando UObject
Antes de expandir nossa classe com recursos, precisamos criá-la. Vamos usar o editor para que o gerador grave automaticamente tudo o que é necessário para trabalhar no cabeçalho (.h).
Podemos criar uma nova classe no editor do Navegador de conteúdo, clicando no botão
Novo e selecionando
Nova classe C ++ .

Em seguida, precisamos escolher a própria classe. Pode não estar na lista geral; portanto, abra-o e selecione UObject.

Nomeie sua turma e selecione em qual pasta ela será armazenada. Quando criamos a classe, você pode entrar no estúdio, encontrá-la e começar a incorporar todas as funções necessárias.
Iniciantes, observe que dois arquivos são criados: .h e .ccp. Em .h, você declarará variáveis e funções e, em .cpp, definirá sua lógica. Encontre os dois arquivos no seu projeto. Se você não alterou o caminho, eles devem estar em Projeto / Origem / Projeto /.Até continuarmos, vamos escrever o parâmetro
Blueprintable na macro UCLASS () acima da declaração da classe. Você deve obter algo como isto:
.hUCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() }
Graças a isso, você pode criar Blueprints que herdarão tudo o que fazemos com esse objeto.
Replicação de UObject
Por padrão, os UObjects não são replicados pela rede. Como descrevi acima, várias restrições são criadas quando você precisa sincronizar dados ou lógica entre as partes, mas não armazena lixo no mundo.
No Unreal Engine 4, a replicação ocorre precisamente devido a objetos do mundo. Isso significa que simplesmente criar um objeto na memória e replicá-lo falhará. De qualquer forma, você precisará de um proprietário que gerencie a transferência de dados do objeto entre o servidor e os clientes. Por exemplo, se o seu objeto é a habilidade de um personagem, ele deve se tornar o proprietário. Ele também será o condutor da transmissão de informações pela rede.
Prepare nosso objeto para replicação. Até agora, no cabeçalho, precisamos definir apenas uma função:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; }
IsSupportedForNetworking () determinará que o objeto suporta a rede e pode ser replicado.
No entanto, nem tudo é tão simples. Como escrevi acima, você precisa de um proprietário que controle a transferência do objeto. Para a pureza do experimento, crie um AActor que o replique. Isso pode ser feito exatamente da mesma maneira que UObject, apenas a classe pai, naturalmente, AActor.
Iniciantes, se você precisar replicar um objeto em um caractere, controlador ou outro local, crie a classe base apropriada por meio do editor, adicione a lógica necessária e já herda dessa classe no Blueprints.Dentro de nós precisamos de 3 funções: um construtor, uma função para replicar subobjetos, uma função que determina o que é replicado dentro deste AActor (variáveis, referências a objetos, etc.) e o local onde criamos nosso objeto.
Não se esqueça de criar uma variável pela qual nosso objeto será armazenado:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: AMyActor(); virtual bool ReplicateSubobjects (class UActorChannel *Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; void GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) const override; virtual void BeginPlay (); UPROPERTY(Replicated, BlueprintReadOnly, Category="Object") class UMyObject* MyObject; }
Dentro do arquivo de origem, temos que escrever tudo:
.cpp
Agora seu objeto será replicado com este ator. Você pode exibir o nome dele no tick, mas já no cliente. Observe que, no Begin Play, é improvável que um objeto chegue antes do cliente; portanto, não faz sentido escrever um log nele.
Replicação de variáveis no UObject
Na maioria dos casos, não faz sentido replicar um objeto se ele não contiver informações que também serão sincronizadas entre o servidor e os clientes. Como nosso objeto já está replicado, a passagem de variáveis não é difícil. Isso é feito da mesma maneira que dentro do nosso ator:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; void GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) const override; UPROPERTY(Replicated, BlueprintReadWrite, Category="Object") int MyInteger;
.cpp
Ao adicionar uma variável e sinalizá-la para replicação, podemos replicá-la. Tudo é simples e o mesmo que no AActor.
No entanto, existe uma pequena armadilha que não é imediatamente visível, mas pode ser enganosa. Isso será especialmente perceptível se você estiver criando seu UObject não para trabalhar em C ++, mas preparando-o para herança e trabalho em Blueprints.
A conclusão é que as variáveis criadas no herdeiro do Blueprints não serão replicadas. O mecanismo não as marca automaticamente e a alteração de um parâmetro no servidor no BP não altera nada no valor no cliente. Mas existe uma cura para isso. Para a replicação correta das variáveis BP, é necessário marcá-las com antecedência. Adicione algumas linhas ao GetLifetimeReplicatedProps ():
.cpp void UMyObject ::GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) { Super::GetLifetimeReplicatedProps(OutLifetimeProps);
Variáveis nas classes filho Blueprint agora serão replicadas conforme o esperado.
Eventos RPC no UObject
Eventos RPC (chamada de procedimento remoto) são funções especiais chamadas no outro lado da interação de rede de um projeto. Utilizando-os, você pode chamar a função do servidor em outros clientes e do cliente no servidor. Muito útil e frequentemente usado ao escrever projetos de rede.
Se você não estiver familiarizado com eles, recomendo a leitura de um artigo. Ele descreve o uso em C ++ e em Blueprints .Embora não haja problemas no Actor ou nos componentes com sua chamada, os eventos do UObject são acionados do mesmo lado em que foram chamados, o que torna impossível fazer uma chamada remota quando necessário.
Observando o código do componente (UActorComponent), podemos encontrar várias funções que permitem transferir chamadas pela rede. Como o UActorComponent é herdado do UObject, podemos simplesmente copiar as seções necessárias de código e colar em nosso objeto para que ele funcione da seguinte maneira:
.h
.cpp
Com essas funções, poderemos acionar eventos RPC não apenas no código, mas também nos blueprints.
Observe que, para acionar eventos de cliente ou servidor, você precisa de um proprietário cujo proprietário seja nosso jogador. Por exemplo, o objeto pertence ao personagem do usuário ou ao objeto em que Owner é o Player Controller do jogador.
Recursos globais em projetos
Se você já criou um Object Blueprint, pode ter notado que não pode chamar funções globais (estáticas, mas por uma questão de clareza, chamamos isso) disponíveis em outras classes, por exemplo, GetGamemode (). Parece que você simplesmente não pode fazer aulas nas classes Object, pelas quais você precisa passar todos os links ao criar, ou de alguma forma perverter, e às vezes a escolha recai completamente na classe Actor que é criada no palco e suporta tudo.
Mas em C ++, é claro, não existem esses problemas. No entanto, o designer do jogo, que brinca com as configurações e adiciona pequenas coisas diferentes, não pode dizer que você precisa abrir o Visual Studio, encontrar a classe apropriada e obter o modo de jogo na função doSomething () alterando os pontos nela. Portanto, é imperativo que o designer possa fazer login no Bluprint e, com dois cliques, fazer o que é seu trabalho. Economize o tempo dele e o seu. No entanto, os Blueprints foram inventados para isso.
A conclusão é que, quando você procura ou chama funções no menu de contexto no Bluprint, essas mesmas funções globais que exigem uma referência ao mundo tentam chamar uma função dentro do seu objeto que se refere a ela. E se o editor vê que não há função, ele entende que não pode usá-lo e não o mostra na lista.

No entanto, existe uma cura para isso. Até dois.
Vamos primeiro considerar uma opção para uso mais conveniente no editor. Precisamos redefinir uma função que retorne um link para o mundo e, em seguida, o editor entenderá que no próprio jogo ele pode funcionar:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
.cpp UWorld* UMyObject::GetWorld() const {
Agora está definido e o editor entenderá que, em geral, o objeto é capaz de obter o ponteiro desejado (embora não seja válido) e usar funções globais no BP.
Observe que o proprietário (GetOuter ()) também deve ter acesso ao mundo. Pode ser outro UObject com um objeto GetWorld (), componente ou ator específico na cena.
Existe outro caminho. Basta adicionar um rótulo à macro UCLASS () ao declarar a classe que o parâmetro WorldContextObject será adicionado às funções estáticas no BP, nas quais qualquer objeto que sirva como condutor do "mundo" e as funções globais do mecanismo sejam alimentados. Essa opção é adequada para quem no projeto pode ter vários mundos ao mesmo tempo (por exemplo, o mundo do jogo e o mundo para o espectador):
.h
Se você digitar GetGamemode na pesquisa no BP, ele aparecerá na lista, como outras funções semelhantes, e o parâmetro será WorldContextObject, no qual você precisará passar um link para o Actor.

A propósito, você pode simplesmente registrar o proprietário de nossa propriedade lá. Eu recomendo criar uma função no Actor, sempre será útil para o objeto:
.h UCLASS(Blueprintable, meta=(ShowWorldContextPin)) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
Agora você pode simplesmente usar as funções globais em combinação com a nossa função Pure para obter o proprietário.

Se você também declarar GetWorld () na segunda variante como na primeira variante, poderá enviar uma referência a si mesmo (Próprio ou Este) no parâmetro WorldContextObject.

Eventos BeginPlay e Tick
Outro problema que os desenvolvedores do Blueprint podem encontrar é que não há eventos BeginPlay e Tick na classe Object. Claro, você pode criá-los e ligar de outra classe. Mas você deve admitir que é muito mais conveniente quando tudo funciona imediatamente.
Vamos começar entendendo como fazer o Begin Play. Podemos criar uma função disponível para reescrita no BP e chamá-la no construtor da classe, mas existem vários problemas, pois no momento do construtor seu objeto ainda não foi totalmente inicializado.
Em todas as classes, existe a função PostInitProperties (), que é chamada após a inicialização da maioria dos parâmetros e o registro do objeto em vários sistemas internos, por exemplo, para o coletor de lixo. Nele, você pode simplesmente chamar nosso evento, que será usado no Blueprints:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
.cpp void UMyObject::PostInitProperties() { Super::PostInitProperties();
Em vez de if (GetOuter () && GetOuter () -> GetWorld ()), você pode simplesmente colocar if (GetWorld ()) se você já o redefiniu.
Cuidado! Por padrão, PostInitProperties () também é chamado no editor.
Agora podemos entrar no nosso objeto BP e chamar o evento BeginPlay. Será chamado quando o objeto for criado.
Vamos para o evento Tick. Não existe uma função simples para nós. Assinale os objetos no mecanismo que chama um gerente especial, para o qual você precisa, de alguma forma, buscar. No entanto, há um truque muito conveniente aqui - herança adicional de FTickableGameObject. Isso permitirá que você faça automaticamente tudo o que precisa e, em seguida, será suficiente apenas para captar as funções necessárias:
.h
.cpp void UMyObject::Tick(float DeltaTime) {
Se você herdar do seu objeto e criar uma classe BP, um evento EventTick estará disponível, o que causará lógica para cada quadro.
Adicionando componentes de UObjects
No UObject Blueprints, você não pode gerar componentes para atores. O mesmo problema é inerente às plantas do ActorComponent. A lógica da Epic Games não é muito clara, pois em C ++ isso pode ser feito. Além disso, você pode adicionar um componente do Actor a outro objeto do Actor simplesmente especificando um link. Mas isso não pode ser feito.
Infelizmente, não consegui descobrir este item. Se alguém tiver instruções sobre como fazer isso, ficarei feliz em publicá-lo aqui.
A única opção que posso oferecer no momento é criar um invólucro na classe UObject, fornecendo acesso a uma simples adição de componentes. Assim, será possível adicionar componentes ao ator, mas você não terá parâmetros de entrada criados dinamicamente da semente. Muitas vezes, isso pode ser negligenciado.
Configurando uma Instância Através do Editor
No UE4, há outro "recurso" conveniente para trabalhar com objetos - essa é a capacidade de criar uma instância durante a inicialização e alterar seus parâmetros através do editor, definindo assim suas propriedades, sem criar uma classe filho apenas por uma questão de configurações. Especialmente útil para designers de jogos.
Suponha que você tenha um gerenciador de modificadores para um personagem e os próprios modificadores sejam representados por classes que descrevem os efeitos sobrepostos. O designer do jogo criou um par de modificadores e indica no gerente quais são usados.
Em uma situação normal, ficaria assim:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere) TSubclassOf<class UMyObject> MyObjectClass; }

No entanto, há um problema, pois ele não pode configurar modificadores e você deve criar uma classe adicional para outros valores. Concordo, não é muito conveniente ter dezenas de classes no Navegador de conteúdo que diferem apenas em valores. Consertar isso é fácil. Você pode adicionar alguns campos dentro de USTRUCT () e também indicar no objeto contêiner que nossos objetos serão instâncias, e não apenas referências a objetos ou classes inexistentes:
.h UCLASS(Blueprintable, DefaultToInstanced, EditInlineNew)
Isso por si só não é suficiente, agora é necessário indicar que a mesma variável com a classe será uma instância. Isso já foi feito onde você armazena o objeto, por exemplo, no gerenciador de modificadores de caracteres:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Instanced)
Observe que usamos a referência ao objeto, e não à classe, pois a instância será criada imediatamente após a inicialização. Agora podemos entrar na janela do editor para selecionar uma classe e ajustar os valores dentro da instância. É muito mais conveniente e mais flexível.

Informações
Há outra classe interessante no Unreal Engine. Isto é AInfo. Uma classe herdada do AActor que não possui representação visual no mundo. O Info usa classes como: modo de jogo, GameState, PlayerState e outras. Ou seja, classes que suportam chips diferentes do AActor, por exemplo, replicação, mas não são colocadas em cena.
Se você precisar criar um gerente global adicional que deva suportar a rede e todas as classes de atores resultantes, poderá usá-lo. Você não precisa manipular a classe UObject conforme descrito acima para forçá-la, por exemplo, a replicar dados.
No entanto, lembre-se de que, embora o objeto não tenha coordenadas, componentes visuais e não seja renderizado na tela, ele ainda é um descendente da classe Actor, o que significa que é tão pesado quanto o pai. Usado razoavelmente em pequenas quantidades e por conveniência.
Conclusão
O UObject é necessário com muita frequência e eu aconselho você a usá-lo sempre que o Ator não for realmente necessário. É uma pena que seja um pouco limitado, mas também é uma vantagem. Às vezes, é necessário mexer na necessidade de usar um modelo personalizado, mas o mais importante é que todas as principais restrições podem ser removidas.
, , UObject, , , .
, , Unreal Engine 4. - , . , - , UObject.