UE : Support Multi Gamepads on Mac

1. Background

Unreal Engine Source Build doesn’t work with the multiple gamepads on Mac.

2. Implementation

1. Engine/Source/Runtime/ApplicationCore/Private/Apple/AppleControllerInterface.h

...

enum PlayerIndex
{
	PlayerOne,
	PlayerTwo,
	PlayerThree,
	PlayerFour,

+	LastPlayerIndex = PlayerFour,
+	MaxPlayerCount = LastPlayerIndex + 1,
	
	PlayerUnset = -1
};

...

class FAppleControllerInterface : public IInputInterface
{
	...
	
    void SetCurrentController(GCController* Controller);

+	PlayerIndex GetPlayerIndex(GCController* Controller) const;
+	PlayerIndex GetAvailablePlayerIndex() const;
+	bool ReplacePlayerOne(PlayerIndex NewPlayerIndex);
+	bool SetPlayerIndex(GCController* Controller, PlayerIndex NewPlayerIndex);
	
    ...
};

2. Engine/Source/Runtime/ApplicationCore/Private/Apple/AppleControllerInterface.cpp

...

void FAppleControllerInterface::SetCurrentController(GCController* Controller)
{
+   const PlayerIndex CurPlayerIndex = GetPlayerIndex(Controller);
+
+	// The controller is already PlayerOne, the current controller.
+	if (CurPlayerIndex == PlayerIndex::PlayerOne)
+	{
+		return;
+	}
+
+	// Error: Invalid Controller.
+	if(CurPlayerIndex == PlayerIndex::PlayerUnset)
+	{
+		UE_LOG(LogAppleController, Warning, TEXT("Controller is not connected."));
+		return;
+	}
+	
+	// Replace existing PlayerOne with the Controller's PlayerIndex. 
+	if(!ReplacePlayerOne(CurPlayerIndex))
+	{
+		UE_LOG(LogAppleController, Warning, TEXT("Failed to replace PlayerOne to PlayerIndex %d."), static_cast<int32>(CurPlayerIndex));
+		return;
+	}
+
+	// Set the controller to PlayerOne
+	if (!SetPlayerIndex(Controller, PlayerIndex::PlayerOne))
+	{
+		UE_LOG(LogAppleController, Warning, TEXT("Failed to set PlayerOne to Controller %s."));
+		return;
+	}
}

+PlayerIndex FAppleControllerInterface::GetPlayerIndex(GCController* Controller) const
+{
+	for (int32 ControllerIndex = 0; ControllerIndex < UE_ARRAY_COUNT(Controllers); ControllerIndex++)
+	{
+		if (Controllers[ControllerIndex].Controller == Controller)
+		{
+			return Controllers[ControllerIndex].PlayerIndex;
+		}
+	}
+
+	UE_LOG(LogAppleController, Warning, TEXT("Controller %@ is not connected"), Controller.productCategory);
+	return PlayerIndex::PlayerUnset;
+}

+PlayerIndex +FAppleControllerInterface::GetAvailablePlayerIndex() const
+{
+	bool UsedPlayerIndices[MaxPlayerCount] = { false };
+	for (int32 ControllerIndex = 0; ControllerIndex < UE_ARRAY_COUNT(Controllers); ControllerIndex++)
+	{
+		if (Controllers[ControllerIndex].PlayerIndex != PlayerIndex::PlayerUnset)
+		{
+			UsedPlayerIndices[Controllers[ControllerIndex].PlayerIndex] = true;
+		}
+	}
+
+	// Return the first available player index.
+	for (int32 CandidatePlayerIndex = 0; CandidatePlayerIndex <= LastPlayerIndex; CandidatePlayerIndex++)
+	{
+		if (UsedPlayerIndices[CandidatePlayerIndex] == false)
+		{
+			return static_cast<PlayerIndex>(CandidatePlayerIndex);
+		}
+	}
+
+	// No available player index found;
+	return PlayerIndex::PlayerUnset;
+}

+bool FAppleControllerInterface::ReplacePlayerOne(PlayerIndex NewPlayerIndex)
+{
+	for (int32 ControllerIndex = 0; ControllerIndex < UE_ARRAY_COUNT(Controllers); ControllerIndex++)
+	{
+		if (Controllers[ControllerIndex].PlayerIndex == PlayerIndex::PlayerOne)
+		{
+			Controllers[ControllerIndex].PlayerIndex = NewPlayerIndex;
+			return true;
+		}
+	}
+
+	return false;
+}

+bool FAppleControllerInterface::SetPlayerIndex(GCController* Controller, PlayerIndex NewPlayerIndex)
+{
+	for (int32 ControllerIndex = 0; ControllerIndex < UE_ARRAY_COUNT(Controllers); ControllerIndex++)
+	{
+		if (Controllers[ControllerIndex].Controller == Controller)
+		{
+			Controllers[ControllerIndex].PlayerIndex = NewPlayerIndex;
+			return true;
+		}
+	}
+
+	return false;
+}

void FAppleControllerInterface::HandleConnection(GCController* Controller)
{
	...

+	const PlayerIndex NewPlayerIndex = GetAvailablePlayerIndex();
+	if (NewPlayerIndex == PlayerIndex::PlayerUnset)
+	{
+		UE_LOG(LogAppleController, Log, TEXT("Maximum player count %d reached."), MaxPlayerCount);
+		return;
+	}
	
	...
	for (int32 ControllerIndex = 0; ControllerIndex < UE_ARRAY_COUNT(Controllers); ControllerIndex++)
	{
        ...
        
+       Controllers[ControllerIndex].PlayerIndex = NewPlayerIndex;
        ...
        
+       UE_LOG(LogAppleController, Log, TEXT("New %s controller inserted, assigned to playerIndex %d"),
               Controllers[ControllerIndex].ControllerType == ControllerType::SiriRemote
               ? TEXT("Remote") : TEXT("Gamepad"), Controllers[ControllerIndex].PlayerIndex);
        break;
	}
	...
}

...

UE : ClassDefaultObject has valid properties

1. Background

I needed to know if the ClassDefaultObject of a Blueprint asset will show the overwritten properties or only the default properties.

The Blueprint is a child of a C++ class inheriting AActor. For example, the Blueprint ‘BP_VehicleItem’ is a child of AVehicleItem, and the AVehicleItem is a child of AActor.

2. Implementation

1) C++ Classes

// VehicleInteraction.h
UCLASS()
class SPAWNACTOR_API AVehicleInteraction : public AActor
{
	...

	UPROPERTY(EditDefaultsOnly)
	FString VehicleItemPath;

protected:
	virtual void BeginPlay() override;

	...

private:
	TWeakObjectPtr<class AVehicleItem> SpawnedVehicleItem;

	TWeakObjectPtr<class AVisualActor> SpawnedVisualActorFromCDO;
	TWeakObjectPtr<class AVisualActor> SpawnedVisualActorFromObject;
};

// VehicleInteraction.cpp
void AVehicleInteraction::BeginPlay()
{
	Super::BeginPlay();

	const FSoftClassPath BPClassPath = FSoftClassPath(VehicleItemPath + "_C");
	if (UClass* Class = BPClassPath.TryLoadClass<AVehicleItem>(); IsValid(Class))
	{
		if (auto* DefaultObject = Class->GetDefaultObject<AVehicleItem>())
		{
			UE_LOG(LogTemp, Display, TEXT("%s AVehicleItem's DefaultObject %s's Visual Actor is valid %d."), ANSI_TO_TCHAR(__FUNCTION__),
				*DefaultObject->GetName(),
				IsValid(DefaultObject->VisualActor));

			if (IsValid(DefaultObject->VisualActor))
			{
				SpawnedVisualActorFromCDO = GetWorld()->SpawnActor<AVisualActor>(DefaultObject->VisualActor, FVector::ZeroVector, FRotator::ZeroRotator);
				if (SpawnedVisualActorFromCDO.IsValid())
				{
					UE_LOG(LogTemp, Display, TEXT("%s SpawnedVisualActorFromCDO %s's Data %s."), ANSI_TO_TCHAR(__FUNCTION__),
						*SpawnedVisualActorFromCDO.Get()->GetName(),
						*SpawnedVisualActorFromCDO.Get()->VisualDataPath);
				}
			}
		}
	
		SpawnedVehicleItem = GetWorld()->SpawnActor<AVehicleItem>(Class, FVector::ZeroVector, FRotator::ZeroRotator);
		if (SpawnedVehicleItem.IsValid())
		{
			auto* SpawnedObject = SpawnedVehicleItem.Get();
			UE_LOG(LogTemp, Display, TEXT("%s AVehicleItem's SpawnedObject %s's Visual Actor is valid %d."), ANSI_TO_TCHAR(__FUNCTION__),
				*SpawnedObject->GetName(),
				IsValid(SpawnedObject->VisualActor));

			if (IsValid(SpawnedObject->VisualActor))
			{
				SpawnedVisualActorFromObject = GetWorld()->SpawnActor<AVisualActor>(SpawnedObject->VisualActor, FVector::ZeroVector, FRotator::ZeroRotator);
				if (SpawnedVisualActorFromObject.IsValid())
				{
					UE_LOG(LogTemp, Display, TEXT("%s SpawnedVisualActorFromObject %s's Data %s."), ANSI_TO_TCHAR(__FUNCTION__),
						*SpawnedVisualActorFromObject.Get()->GetName(),
						*SpawnedVisualActorFromObject.Get()->VisualDataPath);
				}
			}
		}
	}
}

// VehicleItem.h
UCLASS(Blueprintable, Abstract)
class SPAWNACTOR_API AVehicleItem : public AActor
{
	...
	
	UPROPERTY(EditDefaultsOnly)
	TSubclassOf<AVisualActor> VisualActor;

	...
};

// VisualActor.h
UCLASS()
class SPAWNACTOR_API AVisualActor : public AActor
{
	...

	UPROPERTY(EditDefaultsOnly)
	FString VisualDataPath;
	
	...
};

2) Blueprint Setup

BP_VehicleInteraction : AVehicleInteraction
BP_VehicleItem : AVehicleItem
BP_VisualActor : AVisualActor

3) Level Setup

3. Result

Output Log from BP_VehicleInteraction::BeginPlay()

As you can see from the Output Log, the ClassDefaultObject has valid properties overwritten.

See BP_VehicleItem’s CDO has overwritten TSubclassOf VisualActor, so I could spawn a VisualActor out of it.

In conclusion, to use a blueprint for the data only, we don’t need to spawn it to World, but read it from its ClassDefaultObject.

No compiler was found. In order to use a C++ template, you must first install Xcode.

TL;DR
Install Command Line Tool by running this command in Terminal.

% xcode-select --install

Explanation
After macOS Monterey v12.3 and Xcode v13.3 update, I got an error while creating new C++ template project for Unreal Engine 4.27;

No compiler was found. In order to use a C++ template, you must first install Xcode.

To fix this error, I ran the command ‘xcode-select –install’ in Terminal app, and it started installing ‘Command Line Tool’. After installing the tool, it resolved the problem.

For the record, before installing ‘Command Line Tool’, ‘xcode-select -p’ was directing to the right location which is ‘/Applications/Xcode.app/Contents/Developer’.

Error : variable ‘LayerNames’ set but not used

TL;DR
Add these code to [ProjectName].target.cs and [ProjectName]Editor.target.cs files.

bOverrideBuildEnvironment = true;
AdditionalCompilerArguments = "-Wno-unused-but-set-variable";
// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;
using System.Collections.Generic;

public class ActionRPGTarget : TargetRules
{
	public ActionRPGTarget(TargetInfo Target)
		: base(Target)
	{
		Type = TargetType.Game;
		ExtraModuleNames.AddRange(new string[] { "ActionRPG" });

		DefaultBuildSettings = BuildSettingsVersion.V2;
		
		bOverrideBuildEnvironment = true;
		AdditionalCompilerArguments = "-Wno-unused-but-set-variable";
	}
}

Explanation
After macOS Monterey v12.3 and Xcode v13.3 update, I got errors while compiling Unreal Engine project; ‘Error : variable ‘LayerNames’ set but not used [-Werror,-Wunused-but-set-variable]’.

[2/46] Compile SharedPCH.Engine.ShadowErrors.h
In file included from /Volumes/KkabNVMe/UnrealEngine/Workspace/ActionRPG/Intermediate/Build/Mac/x86_64/ActionRPGEditor/Development/Engine/SharedPCH.Engine.ShadowErrors.h:284:
In file included from /Users/Shared/UnrealEngine/UE_4.27/Engine/Source/Runtime/Engine/Public/EngineSharedPCH.h:572:
0>/Users/Shared/UnrealEngine/UE_4.27/Engine/Source/Runtime/Engine/Classes/Materials/Material.h:1279:26: Error  : variable 'LayerNames' set but not used [-Werror,-Wunused-but-set-variable]
                                const TArray<FText>* LayerNames = &LayersExpression->GetLayerNames();
                                                     ^
In file included from /Volumes/KkabNVMe/UnrealEngine/Workspace/ActionRPG/Intermediate/Build/Mac/x86_64/UE4Editor/Development/ActionRPG/PCH.ActionRPG.h:283:
In file included from /Volumes/KkabNVMe/UnrealEngine/Workspace/ActionRPG/Source/ActionRPG/Public/ActionRPG.h:11:
In file included from /Users/Shared/UnrealEngine/UE_4.27/Engine/Source/Runtime/Engine/Public/EngineMinimal.h:111:
0>/Users/Shared/UnrealEngine/UE_4.27/Engine/Source/Runtime/Engine/Classes/Materials/Material.h:1279:26: Error  : variable 'LayerNames' set but not used [-Werror,-Wunused-but-set-variable]
                                const TArray<FText>* LayerNames = &LayersExpression->GetLayerNames();

To fix this error, I needed to update all target.cs files adding two lines of code above.

Perforce Tip – How to recover too many locally deleted files quickly

If you deleted too many Perforce managed files locally like I did(I deleted a UE4/Engine/Binaries folder by accident, which has 26,958 files), try Force Sync instead of Reconcile Offline Work. It’s way faster.

1. p4 sync -f in Command Prompt

cmd> p4 sync -f

2. Or Get Revision – Enable Force Operation in P4V

Happy Version Controlling!

C++ Smart Pointers

Hi, C++ programmers!

I recently got curious about C++ smart pointers like std::unique_ptr and std::shared_ptr, about how it works and when to use. Since we use custom memory managements for game objects, I hardly find the needs of using c++ smart pointer (and not sure they will be deprecated in the future like auto_ptr did).

But why not I gave it a try to read book Effective Modern C++ by Scott Meyers, Chapter 4. Smart Pointers. Here are the summaries (on going).

std::unique_ptr

//////////////////////////////////////////
// std::unique_ptr usage
// – factory function return type for
// objects in a hierarchy.
//////////////////////////////////////////
//////////////////////////////////////////
// factory function with custom deleter
//////////////////////////////////////////
class Investment
{
public:
virtual ~Investment();
};
class Stock : public Investment { … };
class Bond : public Investment { … };
class RealEstate : public Investment { … };
audo deleteInvestment = [](Investment* pInvestment)
{
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename… Ts>
auto makeInvestment(Ts&&… params) // C++14
{
std::unique_ptr<Investment, decltype(deleteInvestment)>
pInv(nullptr, deleteInvestment);
if ( /* a Stock object should be created */ )
{
pInv.reset(new Stock(std::forward<Ts>(params)…);
}
else if ( /* a Bond object should be created */ )
{
pInv.reset(new Bond(std::forward<Ts>(params)…);
}
else if ( /* a RealEstate object should be created */ )
{
pInv.reset(new RealEstate(std::forward<Ts>(params)…);
}
return pInv;
}
//////////////////////////////////////////
// usage
//////////////////////////////////////////
void usage()
{
// pInvestment is of type std::unique_ptr<Investment>
auto pInvestment = makeInvestment(arguments);
}
view raw unique_ptr.cpp hosted with ❤ by GitHub
  • Exclusive-ownership resource management
  • Move-only type
  • Usages : Factory Function, Pimpl Idiom

Happy C++ Programming!

My Inventory
Workstation : Apple 16″ MacBook Pro, 8-Core i9 2.4GHz, 32GB RAM, Radeon Pro 5500M 8GB
Keyboard : Happy Hacking Keyboard Professional BT
Mouse : Logitech MX Ergo Wireless Trackball Mouse

Game Console : Nintendo Switch with Neon Blue and Neon Red Joy‑Con
Currently Playing : Shovel Knight: Treasure Trove
Recently Finished : Bloodstained: Ritual of the Night

Algorithms Unlocked Summary

Hello, computer scientists!

I started reading the book Algorithms Unlocked by Thomas H. Cormen and I found that it is really useful to understand when I need the algorithm. So I will summarise algorithms here by usage and example.

Dijkstra’s Algorithm p.92
– single-source shortest path
– example : finding shortest path to an item on the map
– condition : nonnegative-weight edges graph only

The Bellman-Ford Algorithm p.101
– single-source shortest path
– example : finding arbitrage opportunites in currency trading
– condition : nonnegative-weight edges graph, and negative-weight edges graph both

I’ll keep updating this post.

Happy Coding with Algorithm!

My Inventory
Workstation : Apple 16″ MacBook Pro, 8-Core i9 2.4GHz, 32GB RAM, Radeon Pro 5500M 8GB
Keyboard : Happy Hacking Keyboard Professional BT
Mouse : Logitech MX Ergo Wireless Trackball Mouse

Game Console : Nintendo Switch with Neon Blue and Neon Red Joy‑Con
Currently Playing : Shovel Knight: Treasure Trove
Recently Finished : Bloodstained: Ritual of the Night

OBS High-Quality Video Setup for YouTube

Hi game players,

I started playing the blockbuster game Cyberpunk 2077 which I’d been waiting for a long long time. Since it is cool game, I started recording my gameplay and uploading it to YouTube.

While using OBS for recording, I found that the default output video quality was not enough for gameplay video. So I googled how to setup the OBS’s recording quality to make the result look ok. Here are the changes I made.

  1. Go to Settings
  2. Go to Output
    1. Set Output Mode to Advanced
    2. Select Recording tap
      1. Set Recording Format to mkv; we will remux it to mp4 after finishing recording
      2. Set Encoder to NVIDA NVENC H.264 (new)
      3. Disable Rescale Output
      4. Set Rate Control to CQP
        1. Set CQ Level to 20
        2. Set Preset to Quality
        3. Set Profile to high
        4. Disable Look-ahead
        5. Enable Psycho Visual Turning
        6. Set GPU to 0
        7. Set Max B-frames to 2
  3. Go to Video
    1. Set Base (Canvas) Resolution to what you want; in my case, 2560×1440
    2. Set Output (Canvas) Resolution to what you want; in my case, 2560×1440
    3. Set Downscale Filter to Bicubic (Sharpened scaling, 16 samples)
    4. Set Common FPS Values to 60
OBS Settings
OBS Settings
OBS Scene
OBS Scene Setup

With this setup, when I record 1 hour of video, I get good quality video with 10GB size. I’m quite happy with the result.

You can check my YouTube video here : link

My first Cyberpunk 2077 YouTube video
My first Cyberpunk 2077 YouTube video

Happy Playing Games!

My PC Specs for Gaming
CPU : Intel(R) Core(TM) i7-9700K 3.60GHz
GPU : NVIDIA GeForce RTX 2070 SUPER
RAM : 64.0 GB
OS : Windows 10

My Inventory
Workstation : Apple 16″ MacBook Pro, 8-Core i9 2.4GHz, 32GB RAM, Radeon Pro 5500M 8GB
Keyboard : Happy Hacking Keyboard Professional BT
Mouse : Logitech MX Ergo Wireless Trackball Mouse

Game Console : Nintendo Switch with Neon Blue and Neon Red Joy‑Con
Currently Playing : Shovel Knight: Treasure Trove
Recently Finished : Bloodstained: Ritual of the Night

How to Lock a Character to YZ Plane in Unreal Engine

Option 1 : Set to lock X axis directly to the character movement component

  1. Open the character’s Blueprint
  2. Select Character Movement component
  3. Find Planer Movement
    • Enable Constain to Plain
    • Set X to Plane Constraint Axis Setting

Option 2 : Set to constraint YZ plane to Global Physics Setting and apply it to the chracter movement component

  1. Open Project Settings
  2. Go to Physics – Simulation
    • Set YZPlane to Default Degrees Of Freedom
  3. Open the character’s Blueprint
  4. Select Character Movement component
  5. Go to Planer Movement
    • Enable Constain to Plain
    • Set Use Global Physics Setting to Plane Constraint Axis Setting

Happy Unreal!

My Inventory
Workstation : Apple 16″ MacBook Pro, 8-Core i9 2.4GHz, 32GB RAM, Radeon Pro 5500M 8GB
Keyboard : Happy Hacking Keyboard Professional BT
Mouse : Logitech MX Ergo Wireless Trackball Mouse

Game Console : Nintendo Switch with Neon Blue and Neon Red Joy‑Con
Currently Playing : Shovel Knight: Treasure Trove
Recently Finished : Bloodstained: Ritual of the Night

3 Practical Tips to Procrastinate Procrastinating

Hi, Game Programmers!

As you already know, doing procrastinating doesn’t mean you are a lazy person. If you were a really lazy person, then you wouldn’t even think about doing it from the start. You are a just perfectionist; which is one of good personalities that pushes your product to be a better quality.

But our life is too short to procrastinate. So I want to introduce some tips I found how to procrastinate procrastinating.

1. Do Not Try to Make a Perfect Decision

It’s impossible to make a perfect decision. Period. Especially in computer programming, there are thousand solutions for one problem, and every solution has its own pros and cons. So the perfect decision does not exist, and only the better decision does. But until you make any decision, you can’t find the better decision. Because the better solution is relative value, and you have nothing to compare.

So stop there, and make any decision right now. Then it is the perfect decision that you can make right now.

2. Create One Smallest Chaos ASAP

I already said, if you are procrastinating it means you are a perfectionist. And a perfectionist cannot stand a chaos on your work. And if the chaos is small enough, then you can have an idea immediately to clean it up. So when you create the smallest chaos on your work, then you can focus on the small chaos, sort it out, and eventually you can break the procrastinating cycle.

These are small chaoses I make when I feel like to procrastinate,

One, Creating .h and .cpp Files for the Current Task by Duplicating Existing Codes. If I procrastinate to do follow work, then it will make my local project unable to build because it will occur duplicated class or symbol compile error. So I, who has compile error allergic, immediately start cleaning up the code. And when I finish I have good source file to start coding for the task.

Two, Writing a Long Unorganised Big Chunk of Comment in the Code. Writing a comment about what I need to do, what is my decision for that, and how my decision will work actually helps me to understand the task better, and to organise my thought better. When I finish writing the comment, then I have a decision (doesn’t matter how good decision it is). And the comment without working code is useless, so I will try to keep my word by writing working code right away.

3. Give Your Brain Time Until It Gets Terribly Bored

Actually the time when you are procrastinating is the time when your brain is processing. It is just the time that you don’t know what next move you should make, or you don’t want to make a move further until you feel like or feel right. It is the time that your brain needs to digest the problem and you need to wait until your brain make decision. Give your brain time.

You already know how to make CPU idle when it’s doing something. Right, you need to wait. Running a windowed Fortnight, playing a 4k YouTube video, or refreshing the Visual Studio project while CPU is busy, doesn’t help but make it worse. Your brain is in that situation when you are procrastinating. Trust your brain and give it sometime.

Those are my practical tips to avoid or shorten procrastination. I hope it works for you too.

Happy Working!

My Inventory
Workstation : Apple 16″ MacBook Pro, 8-Core i9 2.4GHz, 32GB RAM, Radeon Pro 5500M 8GB
Keyboard : Happy Hacking Keyboard Professional BT
Mouse : Logitech MX Ergo Wireless Trackball Mouse

Game Console : Nintendo Switch with Neon Blue and Neon Red Joy‑Con
Currently Playing : Shovel Knight: Treasure Trove
Recently Finished : Bloodstained: Ritual of the Night