Tudo sobre GameDev

Módulos de código na Unreal Engine

Tabela de conteúdo

Oque são módulos de código?

Na Unreal Engine, cada módulo é uma unidade distinta de código com um arquivo C# de construção. Assim como um plugin, um jogo pode ser composto por um ou mais módulos. Cada módulo, corresponde a uma biblioteca dinâmica (DLL), apesar de por padrão, em compilações “shipping” todo o código seja compilado em um único executável.

Porque usar módulos em nosso código?

Por padrão, quando criamos um jogo ou plugin, todo o código está contido no módulo principal e isto é ok para jogos pequenos. No entanto, para jogos maiores há uma série de razões para considerar modularizar o código:

  • Encapsulamento e organização: Construir componentes ou sistemas dentro de seus próprios módulos ajuda a manter o código organizado e com baixa dependência entre módulos.
  • Reutilização de código: Como cada módulo de código é uma unidade que o torna facilmente reutilizável possuindo baixa dependência, separar sistemas logicamente distintos, torna o código mais reutilizável e é considerada uma boa prática.
  • Código específico: Criar módulos específicos ajuda muito na organização e trabalho em equipe. Você pode por exemplo criar um módulo específico para o editor ou separar lógicas que são executadas no cliente e servidor em diferentes módulos.  Ainda que lógicas de pré-processador possam ser utilizadas como #if WITH_EDITOR, isso pode aumentar consideravelmente o tamanho do código e deixá-lo confuso.
  • Código específico por plataforma: Como no exemplo anterior, ainda podemos utilizar macros de pré-processador, mas fica mais “limpo” e organizado se utilizarmos um módulo para cada plataforma e realizar a compilação seletivamente de acordo com a platadorma.

Adicionando um módulo

Estrutura de arquivos e pastas
  1. Dentro da pasta Source do seu projeto, já deve existir uma pasta com o nome do módulo principal de jogo. Crie uma pasta com o nome do seu novo módulo e adicione o arquivo “NomeDoSeuModulo.build.cs“. A partir daqui, NomeDoSeuModulo se refere ao bem… nome do seu módulo.
  2. Dentro do diretório recém-criado, crie os arquivos de cabeçalho e fonte com os nomes do seu módulo: NomeDoSeuModulo.h e NomeDoSeuModulo.cpp.
  3. Se preferir, crie as pastas Public Private para arquivos de cabeçalho e fonte.
Na imagem abaixo, vemos a estrutura de pastas e arquivos que criei para o módulo KubberzGenerator para o jogo KubberzOs arquivos com o final contendo _EnumsAndStructs não são obrigatórios para a criação de um módulo. Eles estão ali simplesmente porque gosto de manter minhas estruturas e enums em arquivos separados para manter a organização.
Estrutura de pastas de um módulo
Estrutura de pastas dos módulos KubberzGenerator do jogo Kubberz.
Configurando/Editando os arquivos
Arquivo Build.cs

Edite o arquivo NomeDoSeuModulo.build.cs substituindo NomeDoSeuModulo pelo nome real do módulo recém criado conforme abaixo:

				
					// Copyright Epic Games, Inc. All Rights Reserved.

/** NomeDoSeuModulo.build.cs */

using UnrealBuildTool;

public class NomeDoSeuModulo : ModuleRules
{
	public NomeDoSeuModulo(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore"});

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
		
		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}
				
			
NomeDoSeuModulo.h

Faça o mesmo com o arquivo NomeDoSeuModulo.h

				
					#pragma once

#include "CoreMinimal.h"
#include "NomeDoSeuModulo.h"

/** NomeDoSeuModulo.h */

class FNomeDoSeuModulo : public IModuleInterface
{
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
};
				
			
NomeDoSeuModulo.cpp
				
					// Copyright Epic Games, Inc. All Rights Reserved.

/** NomeDoSeuModulo.cpp */

#include "NomeDoSeuModulo.h"
#include "Modules/ModuleManager.h"

IMPLEMENT_MODULE(FNomeDoSeuModulo, NomeDoSeuModulo);

void FNomeDoSeuModulo::StartupModule()
{
}

void FNomeDoSeuModulo::ShutdownModule()
{
}

				
			
Arquivo .Uproject

Na sessão modules do seu arquivo de projeto (SeuProjeto.uproject), adicione a seguinte sessão:

				
					// NomeDoSeuProjeto.uproject
...
        "Modules": [
...
        {
            "Name": "NomeDoSeuModulo",
            "Type": "Runtime", // Defina o seu runtime type
            "LoadingPhase": "PostEngineInit", // Defina o seu loading phase
            //Defina as suas dependências adicionais.
            "AdditionalDependencies": [
				"Engine",
				"UMG"
			]
        }
    ]
...
				
			
Arquivos .Target.cs

Ainda na pasta Source de seu projeto, você deve adicionar a dependência ao módulo que foi criado ao arquivo NomeDoSeuProjetoEditor.target.cs O arquivo ficará parecido com o código abaixo:

				
					// Copyright Epic Games, Inc. All Rights Reserved.

/** NomeDoSeuProjetoEditor.Target.cs */

using UnrealBuildTool;
using System.Collections.Generic;

public class NomeDoSeuProjetoEditorTarget : TargetRules
{
	public NomeDoSeuProjetoEditorTarget( TargetInfo Target) : base(Target)
	{
		Type = TargetType.Editor;
		DefaultBuildSettings = BuildSettingsVersion.V2;
		ExtraModuleNames.AddRange( new string[] { "NomeDoModuloPrincipal","NomeDoSeuNovoModulo" } );
	}
}

				
			

Se você deseja que seu módulo também seja visível para os módulos de jogo, também deve adicionar a dependência ao arquivo NomeDoJogo.Target.cs também presente na pasta Source do seu projeto:

				
					// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;
using System.Collections.Generic;

/** NomeDoSeuJogo.Target.cs */

public class NomeDoSeuJogoTarget : TargetRules
{
	public NomeDoSeuJogoTarget( TargetInfo Target) : base(Target)
	{
		Type = TargetType.Game;
		DefaultBuildSettings = BuildSettingsVersion.V2;
		ExtraModuleNames.AddRange( new string[] { "NomeDoModuloPrincipal", "NomeDoSeuNovoModulo" } );
	}
}

				
			

Depois e editar e salvar os arquivos, você deve gerar a solução do Visual Studio novamente para que as alterações tenham efeito.

Dependências entre módulos

Frequentemente precisamos utilizar elementos de um módulo dentro de outro módulo. Sempre que isto for necessário, devemos adicionar explicitamente a dependência no arquivo .build.cs do módulo dependente. Algo como:

 

				
					//[...]
PublicDependencyModuleNames.Add ("NomeDoModulo");
//[...]
				
			

Notas adicionais

Por conta do HotReload ou dependendo do loading phase do seu módulo, as alterações podem não fazer efeito depois da compilação até que você reinicie a engine.

Para resolver isso você pode procurar o seu módulo na aba Modules e recopilá-lo, evitando a necessidade de reiniciar o projeto. Tenha em mente que o HotReload não é perfeito e possui falhas. Na maioria das vezes, funções e estruturas expostas para blueprint “quebram” quando fazemos isso e ainda precisaremos reiniciar a engine.

Para habilitar a aba modules e listar todos os módulos do seu projeto:

Habilitando a aba Modules na Unreal Engine
Habilitando a aba módulos na Unreal Engine 4
Pesquisando um módulo de jogo na aba Modules
Listando os módulos ativos na Unreal Engine 4

Conclusão

Dividir o código do jogo em módulos é essencial para projetos médios/grandes. Eles nos forçam a manter a organização e a baixa interdependência que por si só, ajudam nosso código a ser compreensível e reaproveitável.

Eu gosto de utilizar módulos para códigos que estão intimamente ligados ao jogo. Para códigos e bibliotecas que utilizam componentes de maneira mais genérica, prefiro criar plugins para manter uma “portabilidade” completa. 

Fontes:

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn

Deixe uma resposta