C++ Game Mode Tutorial

C++ Game Mode Tutorial

This tutorial will teach you how to make a simple Gun Game mode for UT using C++. We'll be making a UE4 plugin that defines a child class of AUTGameMode. I recommend reading the C++ Mutator Tutorial, Blueprint_Mutator_Tutorial_-_Instagib and Blueprint_Mutator_Tutorial_-_Low_Grav first. I will be glossing over things covered in depth in other tutorials.

This tutorial is out of date and does not compile in 4.14, working version can be found within Plugins/SampleGameMode


Requirements

  • Engine version: 4.3
  • Skill level: intermediate C++ and blueprint knowledge

Directory Structure

  • If UnrealTournament\Plugins does not already exist, create it
  • Create a directory inside of UnrealTournament\Plugins to hold our game mode, in this case it would be "SampleGameMode"
  • Create "Source" and "Content" directories inside of the SampleGameMode directory
  • Create a "Public" directory and a "Private" directory inside of the last "Source" directory that we made

UPlugin File

  • We need to create SampleGameMode.uplugin to let UE4 know we have a plugin it should load
  • It should live at the same level that the Source directory does
  • The key difference between this .uplugin and the one in the mutator sample is that EnabledByDefault and CanContainContent are set

SampleGameMode.uplugin - Plugin defintion

  1. {
  2. 	"FileVersion" : 3,
  3.  
  4. 	"FriendlyName" : "Sample Game Mode",
  5. 	"Version" : 1,
  6. 	"VersionName" : "1.0",
  7. 	"CreatedBy" : "Epic Games, Inc.",
  8. 	"CreatedByURL" : "http://epicgames.com",
  9. 	"EngineVersion" : "4.3.0",
  10. 	"Description" : "Sample Game Mode",
  11. 	"Category" : "UnrealTournament.GameMode",
  12. 	"EnabledByDefault" : true,
  13. 	"CanContainContent" : true,
  14.  
  15. 	"Modules" :
  16. 	[
  17. 		{
  18. 			"Name" : "SampleGameMode",
  19. 			"Type" : "Runtime",
  20. 			"WhitelistPlatforms" : [ "Win32", "Win64" ]
  21. 		}
  22. 	]
  23. }

Build.cs File

  • We need to create SampleGameMode.Build.cs so that Unreal Build tool knows how to generate our dll file
  • It should live at the same level that the Public and Private directories do
  • We'll use the PrivateIncludesPaths array to make #include cleaner throughout our source files

SampleGameMode.Build.cs - Unreal Build Tool definitions

  1. namespace UnrealBuildTool.Rules
  2. {
  3. 	public class SampleGameMode : ModuleRules
  4. 	{
  5. 		public SampleGameMode(TargetInfo Target)
  6. 		{
  7. 			PrivateIncludePaths.Add("SampleGameMode/Private");
  8.  
  9. 			PublicDependencyModuleNames.AddRange(
  10. 				new string[]
  11. 				{
  12. 					"Core",
  13. 					"CoreUObject",
  14. 					"Engine",
  15. 					"UnrealTournament",
  16. 				}
  17. 				);
  18. 		}
  19. 	}
  20. }

Header File

  • Unreal Build Tool requires that we have a shared header file as the first include in every cpp source file in the plugin.
  • We'll put the SampleGameMode.h header file inside of the Public directory
    1. include "SampleGameMode.generated.h" is required by Unreal Header Tool
  • Our header is a bit more complex than the mutator one. We've defined two custom structs that we've made BlueprintType so they'll be visible when we're in the editor.
  • You'll notice that each of the structs is just a TArray and their usage in the ASampleGameMode is just a TArray. UE4 does not currently support TArrays of TArrays so we work around that by housing the inner array inside of a structure.
  • We've exposed the StartingInventories and ScoringDamageTypes TArrays to blueprints so they can be filled with content references later. In order for the editor to see them, we made them UPROPERTYs with BlueprintReadWrite markup

SampleGameMode.h - Sample Mutator class definition

  1. // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
  2. #pragma once
  3.  
  4. #include "Core.h"
  5. #include "Engine.h"
  6. #include "UTWeapon.h"
  7. #include "UTGameMode.h"
  8.  
  9. #include "SampleGameMode.generated.h"
  10.  
  11. USTRUCT(BlueprintType)
  12. struct FStartingInventory
  13. {
  14. 	GENERATED_USTRUCT_BODY()
  15.  
  16. 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GameMode")
  17. 	TArray<TSubclassOf<AUTInventory>> Inventory;
  18. };
  19.  
  20. USTRUCT(BlueprintType)
  21. struct FDamageTypeToProgess
  22. {
  23. 	GENERATED_USTRUCT_BODY()
  24.  
  25. 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameMode")
  26. 	TArray<TSubclassOf<UDamageType>> DamageType;
  27. };
  28.  
  29. UCLASS(Blueprintable, Meta = (ChildCanTick))
  30. class ASampleGameMode : public AUTGameMode
  31. {
  32. 	GENERATED_UCLASS_BODY()
  33.  
  34. 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GameMode")
  35. 	TArray<FStartingInventory> StartingInventories;
  36.  
  37. 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameMode")
  38. 	TArray<FDamageTypeToProgess> ScoringDamageTypes;
  39.  
  40. 	virtual void GiveDefaultInventory(APawn* PlayerPawn) override;
  41. 	virtual void ScoreKill(AController* Killer, AController* Other, TSubclassOf<UDamageType> DamageType) override;
  42. 	virtual void GiveNewGun(AUTCharacter *UTCharacter);
  43. };

Plugin Module Interface

  • Very similar to the mutator sample here, just some name changes

SampleGameModePlugin.cpp - Plugin Module Interface

  1. // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
  2.  
  3. #include "SampleGameMode.h"
  4.  
  5. #include "Core.h"
  6. #include "Engine.h"
  7. #include "ModuleManager.h"
  8. #include "ModuleInterface.h"
  9.  
  10. class FSampleGameModePlugin : public IModuleInterface
  11. {
  12. 	/** IModuleInterface implementation */
  13. 	virtual void StartupModule() override;
  14. 	virtual void ShutdownModule() override;
  15. };
  16.  
  17. IMPLEMENT_MODULE( FSampleGameModePlugin, SampleGameMode )
  18.  
  19. void FSampleGameModePlugin::StartupModule()
  20. {
  21.  
  22. }
  23.  
  24.  
  25. void FSampleGameModePlugin::ShutdownModule()
  26. {
  27.  
  28. }

The Game Mode (C++)

  • Our aim with this tutorial is to make a game mode that functions like "Gun Game", you start with a weapon according to your score and going up in score gives you the next gun in the sequence. Pickups are disabled and so are weapon drops.
  • We'll use C++ to handle giving players the right guns on spawn and giving them the right guns after scoring.
  • We'll use Blueprints to handle disabling weapon drops and pickups.
  • Blueprints will also be used to get content references to weapons and damagetypes as hard coding them in C++ can be brittle.
  • ASampleGameMode::GiveDefaultInventory reads the content reference from StartingInventories that we'll set later in blueprints and spawns that inventory for the spawning player
  • ASampleGameMode::ScoreKill verifies that the killing damage type is equal to the content reference from ScoringDamageTypes and then calls GiveNewGun on the killer
  • ASampleGameMode::GiveNewGun will discard the character's current inventory and give them the proper gun by reading a content reference from StartingInventories

SampleGameMode.cpp

  1. // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
  2.  
  3. #include "SampleGameMode.h"
  4.  
  5. ASampleGameMode::ASampleGameMode(const FPostConstructInitializeProperties& PCIP)
  6. 	: Super(PCIP)
  7. {
  8. }
  9.  
  10. void ASampleGameMode::GiveDefaultInventory(APawn* PlayerPawn)
  11. {
  12. 	// Don't call Super
  13.  
  14. 	AUTCharacter* UTCharacter = Cast<AUTCharacter>(PlayerPawn);
  15. 	if (UTCharacter != nullptr && UTCharacter->GetInventory() == nullptr)
  16. 	{
  17. 		int32 InventoryIndex = PlayerPawn->PlayerState->Score;
  18. 		if (InventoryIndex >= StartingInventories.Num())
  19. 		{
  20. 			InventoryIndex = StartingInventories.Num() - 1;
  21. 		}
  22.  
  23. 		if (StartingInventories.IsValidIndex(InventoryIndex))
  24. 		{
  25. 			for (int32 i = 0; i < StartingInventories[InventoryIndex].Inventory.Num(); i++)
  26. 			{
  27. 				if (StartingInventories[InventoryIndex].Inventory[i] != nullptr)
  28. 				{
  29. 					UTCharacter->AddInventory(GetWorld()->SpawnActor<AUTInventory>(StartingInventories[InventoryIndex].Inventory[i], FVector(0.0f), FRotator(0, 0, 0)), true);
  30. 				}
  31. 			}
  32. 		}
  33. 	}
  34. }
  35.  
  36. void ASampleGameMode::ScoreKill(AController* Killer, AController* Other, TSubclassOf<UDamageType> DamageType)
  37. {
  38. 	// Just a suicide, pass it through
  39. 	if (Killer == Other || Killer == nullptr)
  40. 	{
  41. 		Super::ScoreKill(Killer, Other, DamageType);
  42. 		return;
  43. 	}
  44.  
  45. 	APlayerState* KillerPlayerState = Killer->PlayerState;
  46. 	if (KillerPlayerState)
  47. 	{
  48. 		int32 DamageIndex = KillerPlayerState->Score;
  49. 		if (DamageIndex >= ScoringDamageTypes.Num())
  50. 		{
  51. 			DamageIndex = ScoringDamageTypes.Num() - 1;
  52. 		}
  53.  
  54. 		for (int32 i = 0; i < ScoringDamageTypes[DamageIndex].DamageType.Num(); i++)
  55. 		{
  56. 			if (DamageType == ScoringDamageTypes[DamageIndex].DamageType[i])
  57. 			{
  58. 				Super::ScoreKill(Killer, Other, DamageType);
  59.  
  60. 				// If we changed score, give player a new gun
  61. 				if (DamageIndex != KillerPlayerState->Score)
  62. 				{
  63. 					AUTCharacter* UTCharacter = Cast<AUTCharacter>(Killer->GetPawn());
  64. 					if (UTCharacter != nullptr)
  65. 					{
  66. 						GiveNewGun(UTCharacter);
  67. 					}
  68. 				}
  69.  
  70. 				break;
  71. 			}
  72. 		}
  73. 	}
  74. }
  75.  
  76. void ASampleGameMode::GiveNewGun(AUTCharacter *UTCharacter)
  77. {
  78. 	UTCharacter->DiscardAllInventory();
  79.  
  80. 	int32 InventoryIndex = UTCharacter->PlayerState->Score;
  81. 	if (InventoryIndex >= StartingInventories.Num())
  82. 	{
  83. 		InventoryIndex = StartingInventories.Num() - 1;
  84. 	}
  85.  
  86. 	if (StartingInventories.IsValidIndex(InventoryIndex))
  87. 	{
  88. 		for (int32 i = 0; i < StartingInventories[InventoryIndex].Inventory.Num(); i++)
  89. 		{
  90. 			if (StartingInventories[InventoryIndex].Inventory[i] != nullptr)
  91. 			{
  92. 				UTCharacter->AddInventory(GetWorld()->SpawnActor<AUTInventory>(StartingInventories[InventoryIndex].Inventory[i], FVector(0.0f), FRotator(0, 0, 0)), true);
  93. 			}
  94. 		}
  95. 	}
  96. }

Editor Setup

  • You'll need to enable "Show Plugin Content" in the Content Browser's "View Options" menu

Gamemodetutorial viewplugincontent.png

Mutator Blueprint

Gamemodetutorial mutator.png

Game Mode Blueprint

  • Open up BP_SampleGameMode and go to the default properties tab
  • First, we'll set the Built in Mutators array to reference BP_SampleGameBuiltinMutator

Gamemodetutorial builtin.png

  • Next, we'll set the HUD Class to UTHUD_DM

Gamemodetutorial hudclass.png

  • Third, we'll set up the content references to our guns in the StartingInventories array

Gamemodetutorial startinginventory.png

  • Finally, we'll set up the content references to our damage types in the ScoringDamageTypes array

Gamemodetutorial damagetypes.png

Testing

  • To make typing our command line easier, you should add a game mode alias to DefaultGame.ini
  • Under [/Script/Engine.GameMode] put +GameModeClassAliases=(ShortName="Sample",GameClassName="/SampleGameMode/BP_SampleGameMode.BP_SampleGameMode_C")
  • The game type can now be tested by using ?game=sample instead of ?game=/SampleGameMode/BP_SampleGameMode.BP_SampleGameMode_C
  • Make sure to restart the editor after putting that in the ini
  • I prefer to test in PIE by setting the number of clients to 2, but you can also test via commandline by running a server with "ue4editor unrealtournament -game example_map?game=sample?listen" and a client with "ue4editor unrealtournament -game 127.0.0.1"