Implement Flex Mode on an Unreal Engine Game
Objective
Learn how to implement Flex mode on an Unreal Engine game using Android Jetpack WindowManager and raw Java Native Interface (JNI).
Overview
The flexible hinge and glass display on Galaxy foldable devices, such as the Galaxy Z Fold4 and Galaxy Z Flip4, let the phone remains propped open while you use apps. When the phone is partially folded, it will go into Flex mode.
Apps will reorient to fit the screen, letting you watch videos or play games without holding the phone. For example, you can set the device on a flat surface, like on a table, and use the bottom half of the screen to navigate. Unfold the phone to use the apps in full screen mode, and partially fold it again to return to Flex mode. To provide users with a convenient and versatile foldable experience, developers need to optimize their apps to meet the Flex mode standard.
Set up your environment
You will need the following:
-
Epic Games launcher with Unreal Engine 4 or later
-
Visual Studio or any source code editor
-
Samsung Galaxy Foldable device:
- Galaxy Z Fold2, Z Fold3, or newer
- Galaxy Z Flip, Z Flip3, or newer
-
Remote Test Lab (if physical device is not available)
Requirements:
- Samsung account
- Java Runtime Environment (JRE) 7 or later with Java Web Start
- Internet environment where port 2600 is available
Create and set up your project
After launching Unreal Engine from the Epic Games launcher, follow the steps below to start your project:
-
In the Select or Create New Project window, choose Games as a new project category and click Next.
-
Select Third Person as template, then click Next to proceed.
NoteYou can implement Flex mode on any template or existing projects and use this Code Lab activity as a reference.
-
In the Project Settings window, set the following:
- Type of project: C++
- Target platform: Mobile / Tablet
- Performance characteristics: Scalable 3D or 2D
- Real-time raytracing: Raytracing Disabled
- Include an additional content pack: No Starter Content
- Project name: Tutorial_Project
-
Click Create Project. Wait for the engine to finish loading and open the Unreal Editor.
-
Once the project is loaded, go to Edit > Project Settings > Platforms > Android.
-
Click the Configure Now button if the project is not yet configured for the Android platform.
-
Then, proceed with the following APK Packaging and Build settings:
a. APK Packaging
- Set Target SDK Version to 30.
- Set Orientation to Full Sensor.
- Change the Maximum supported aspect ratio to 2.8 (aspect ratio of Galaxy Z Fold3 in decimal) to prevent black bars from appearing on the cover display. Leave it if your game does not need to use the cover display.
- Enable Use display cutout region?, to prevents black bars at the edge of the main screen. Otherwise, leave it unchecked.
b. Build
- Disable Support OpenGL ES3.1 and enable Support Vulkan.
NoteCurrently, there is a problem with OpenGL ES and the split-screen system being investigated. The only option right now is to turn off OpenGL ES and use Vulkan instead.
Enable native resize event
The resize event of a game when switching between displays is disabled in the engine by default. However, this behavior can be easily enabled by setting Android.EnableNativeResizeEvent=1
in the DeviceProfile
.
Currently, the only way to create a profile for foldable devices is by creating a specific rule for each device. To save time in this Code Lab, enable the native resize event for all Android devices instead.
-
Locate and open the Tutorial_Project > Config folder in File Explorer.
-
Inside the Config folder, create a new folder named Android
.
-
Create a new file called AndroidDeviceProfiles.ini
and open this file in a text editor, such as Visual Studio.
-
Copy below DeviceProfile
code to the newly created AndroidDeviceProfiles.ini
file.
[DeviceProfiles]
+DeviceProfileNameAndTypes=Android,Android
[Android DeviceProfile]
DeviceType=Android
BaseProfileName=
+CVars=r.MobileContentScaleFactor=1.0
+CVars=slate.AbsoluteIndices=1
+CVars=r.Vulkan.DelayAcquireBackBuffer=2
+CVars=r.Vulkan.RobustBufferAccess=1
+CVars=r.Vulkan.DescriptorSetLayoutMode=2
; Don't enable Vulkan by default. Specific device profiles can set this cvar to 0 to enable Vulkan.
+CVars=r.Android.DisableVulkanSupport=1
+CVars=r.Android.DisableVulkanSM5Support=1
; PF_B8G8R8A8
+CVars=r.DefaultBackBufferPixelFormat=0
+CVars=Android.EnableNativeResizeEvent=1
; PreviewAllowlistCVars and PreviewDenyListCVars are arrays of cvars that are included or excluded from being applied in mobile preview.
; If any PreviewAllowlistCVars is set, cvars are denied by default.
PreviewAllowlistCVars=None
This is a copy of the default Android DeviceProfile
from the existing BaseDeviceProfiles.ini
file but with the enabled NativeResizeEvent
console variable (CVars
).
NoteThis step is not required when you only want to implement Flex mode. Yet, it's recommended, to allow applications to run seamlessly from main to cover display without stretching and squashing the game, by enabling the NativeResizeEvent
.
Create a new plugin and import the FoldableHelper
FoldableHelper is a Java file that you can use in different projects. It provides an interface to the Android Jetpack WindowManager library, enabling application developers to support new device form factors and multi-window environments.
Before proceeding, read How to Use Jetpack WindowManager in Android Game Dev and learn the details of how FoldableHelper
uses WindowManager
library to retrieve information about the folded state of the device (FLAT
for Normal mode and HALF-OPENED
for Flex mode), window size, and orientation of the fold on the screen.
Download the FoldableHelper.java
file here:
FoldableHelper.java (5.64 KB)
To import the FoldableHelper.java
file to the project, follow the steps below:
-
Go to Edit > Plugins in the Unreal Editor.
-
Click the New Plugin button and select Blank to create a blank plugin.
-
In the Name field, type Foldables_Tutorial
and click the Create Plugin button.
-
In File Explorer, locate and open Tutorial_Project > Plugins folder.
-
Go to Plugins > Foldables_Tutorial > Source> Foldables_Tutorial > Private and create a new folder called Java
.
-
Copy the FoldableHelper.java
file into Java
folder.
-
Open the Tutorial_Project.sln
file in Visual Studio.
-
In the same Private folder path, add a new filter called Java
.
-
Right-click on the Java
filter and click Add > Existing Item. Locate the FoldableHelper.java
file, then click Add to include this Java file in the build.
Modify Java Activity to use FoldableHelper
Unreal Plugin Language (UPL) is a simple XML-based language created by Epic Games for manipulating XML and returning strings.
Using UPL, you can utilize the FoldableHelper.java
file by modifying the Java Activity and related Gradle files as follows:
-
In Visual Studio, right-click on Source > Foldables_Tutorial folder, then click Add > New Item > Web > XML File (.xml).
-
Create an XML file called Foldables_Tutorial_UPL.xml
.
-
Ensure that the file location is correct before clicking Add.
-
In the newly created XML file, include the FoldableHelper.java
file in the build by copying the Java
folder to the build directory.
<root xmlns:android="http://schemas.android.com/apk/res/android">
<prebuildCopies>
<copyDir src="$S(PluginDir)/Private/Java" dst="$S(BuildDir)/src/com/samsung/android/gamedev/foldable" />
</prebuildCopies>
-
Set up the Gradle dependencies in the build.gradle
file by adding the following in the XML file:
<buildGradleAdditions>
<insert>
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0"
implementation "androidx.core:core:1.7.0"
implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.window:window:1.0.0"
implementation "androidx.window:window-java:1.0.0"
}
android{
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
</insert>
</buildGradleAdditions>
-
Next, modify the gameActivity
:
<gameActivityImportAdditions>
<insert>
<!-- package name of FoldableHelper -->
import com.samsung.android.gamedev.foldable.FoldableHelper;
</insert>
</gameActivityImportAdditions>
<gameActivityOnCreateAdditions>
<insert>
FoldableHelper.init(this);
</insert>
</gameActivityOnCreateAdditions>
<gameActivityOnStartAdditions>
<insert>
FoldableHelper.start(this);
</insert>
</gameActivityOnStartAdditions>
<gameActivityOnStopAdditions>
<insert>
FoldableHelper.stop();
</insert>
</gameActivityOnStopAdditions>
</root>
gameActivityImportAdditions
adds the com.samsung.android.gamedev.foldable.FoldableHelper
into the gameActivity
with the existing imports.
gameActivityOnCreateAdditions
adds the code to the OnCreate()
method inside the gameActivity
.
gameActivityOnStartAdditions
adds the code to the OnStart()
method inside the gameActivity
.
gameActivityOnStopAdditions
adds the code to the OnStop()
method inside the gameActivity
.
-
Save the XML file.
-
Then, ensure that the engine uses the UPL file by modifying the Foldables_Tutorial.Build.cs
script, located in the same folder as the Foldables_Tutorial_UPL.xml
file. After the DynamicallyLoadedModuleNames.AddRange
call, add the following:
if (Target.Platform == UnrealTargetPlatform.Android)
{
AdditionalPropertiesForReceipt.Add("AndroidPlugin", ModuleDirectory + "\\Foldables_Tutorial_UPL.xml");
}
This means that the game engine will use the UPL file if the platform is Android. Otherwise, the FoldableHelper
won’t work.
Implement a storage struct
The next thing to implement is a struct, the native version of Java’s FoldableLayoutInfo
class.
To store the data retrieved from the Java code using a struct, do the following:
-
In Content Browser of Unreal Editor, right-click on C++ Classes > Add/Import Content. Then, click New C++ Class.
-
Select None for the parent class and click Next.
-
Name the new class as FoldableLayoutInfo
.
-
Assign it to the Foldables_Tutorial
plugin. Then, click Create Class.
-
Delete the created FoldableLayoutInfo.cpp
file and only keep its header file.
-
In the header file called FoldableLayoutInfo.h
, set up a struct to store all needed data from the WindowManager
.
#pragma once
#include "Core.h"
enum EFoldState { UNDEFINED_STATE, FLAT, HALF_OPENED };
enum EFoldOrientation { UNDEFINED_ORIENTATION, HORIZONTAL, VERTICAL };
enum EFoldOcclusionType { UNDEFINED_OCCLUSION, NONE, FULL };
struct FFoldableLayoutInfo
{
EFoldState State;
EFoldOrientation Orientation;
EFoldOcclusionType OcclusionType;
FVector4 FoldBounds;
FVector4 CurrentMetrics;
FVector4 MaxMetrics;
bool IsSeparating;
FFoldableLayoutInfo() :
State(EFoldState::UNDEFINED_STATE),
Orientation(EFoldOrientation::UNDEFINED_ORIENTATION),
OcclusionType(EFoldOcclusionType::UNDEFINED_OCCLUSION),
FoldBounds(-1, -1, -1, -1),
CurrentMetrics(-1, -1, -1, -1),
MaxMetrics(-1, -1, -1, -1),
IsSeparating(false)
{ }
};
Implement JNI code
To implement JNI, create a New C++ Class with no parent and name it Foldables_Helper
. Assign the class to the same plugin, then modify the C++ header and source files as follows:
-
In the created header file (Foldables_Helper.h
), include FoldableLayoutInfo.h
.
#include "FoldableLayoutInfo.h"
-
Then, declare a MULTICAST_DELEGATE
to serve as a listener for passing the data from the Java implementation to the rest of the engine.
DECLARE_MULTICAST_DELEGATE_OneParam(FOnLayoutChangedDelegate, FFoldableLayoutInfo);
-
Lastly, set up the methods and member variables.
class FOLDABLES_TUTORIAL_API FFoldables_Helper
{
public:
static void Init();
static bool HasListener;
static FOnLayoutChangedDelegate OnLayoutChanged;
};
-
Moving to the source file (Foldables_Helper.cpp
), set up the definitions for the methods and member variables created in the header file.
bool FFoldables_Helper::HasListener = false;
FOnLayoutChangedDelegate FFoldables_Helper::OnLayoutChanged;
void FFoldables_Helper::Init() {
HasListener = true;
}
-
Now, in the same source file, create the native version of the OnLayoutChanged()
function created in the FoldableHelper.java
file.
-
Since the Java OnLayoutChanged()
function only works on Android, surround the function with an #if
directive to ensure that it compiles only on Android.
#if PLATFORM_ANDROID
#endif
-
Within this directive, copy the code below to use the JNI definition of the Java OnLayoutChanged()
function.
extern "C"
JNIEXPORT void JNICALL
Java_com_samsung_android_gamedev_foldable_FoldableHelper_OnLayoutChanged(JNIEnv * env, jclass clazz,
jobject JfoldableLayoutInfo) {
-
Create the FFoldableLayoutInfo
to store the data retrieved from Java.
FFoldableLayoutInfo result;
-
Retrieve the Field IDs of the FoldableLayoutInfo
and Rect Objects
created in the Java file.
//Java FoldableLayoutInfo Field IDs
jclass jfoldableLayoutInfoCls = env->GetObjectClass(JfoldableLayoutInfo);
jfieldID currentMetricsId = env->GetFieldID(jfoldableLayoutInfoCls, "currentMetrics", "Landroid/graphics/Rect;");
jfieldID maxMetricsId = env->GetFieldID(jfoldableLayoutInfoCls, "maxMetrics", "Landroid/graphics/Rect;");
jfieldID hingeOrientationId = env->GetFieldID(jfoldableLayoutInfoCls, "hingeOrientation", "I");
jfieldID stateId = env->GetFieldID(jfoldableLayoutInfoCls, "state", "I");
jfieldID occlusionTypeId = env->GetFieldID(jfoldableLayoutInfoCls, "occlusionType", "I");
jfieldID isSeparatingId = env->GetFieldID(jfoldableLayoutInfoCls, "isSeparating", "Z");
jfieldID boundsId = env->GetFieldID(jfoldableLayoutInfoCls, "bounds", "Landroid/graphics/Rect;");
jobject currentMetricsRect = env->GetObjectField(JfoldableLayoutInfo, currentMetricsId);
//Java Rect Object Field IDs
jclass rectCls = env->GetObjectClass(currentMetricsRect);
jfieldID leftId = env->GetFieldID(rectCls, "left", "I");
jfieldID topId = env->GetFieldID(rectCls, "top", "I");
jfieldID rightId = env->GetFieldID(rectCls, "right", "I");
jfieldID bottomId = env->GetFieldID(rectCls, "bottom", "I");
-
Retrieve the current WindowMetrics
and store it in the FFoldableLayoutInfo
as an FIntVector4
.
// currentMetrics
int left = env->GetIntField(currentMetricsRect, leftId);
int top = env->GetIntField(currentMetricsRect, topId);
int right = env->GetIntField(currentMetricsRect, rightId);
int bottom = env->GetIntField(currentMetricsRect, bottomId);
// Store currentMetrics Rect to FVector4
result.CurrentMetrics = FIntVector4{ left, top, right, bottom };
-
Do the same for the other variables in the Java FoldableLayoutInfo
.
// maxMetrics
jobject maxMetricsRect = env->GetObjectField(JfoldableLayoutInfo, maxMetricsId);
left = env->GetIntField(maxMetricsRect, leftId);
top = env->GetIntField(maxMetricsRect, topId);
right = env->GetIntField(maxMetricsRect, rightId);
bottom = env->GetIntField(maxMetricsRect, bottomId);
//Store maxMetrics Rect to FVector4
result.MaxMetrics = FIntVector4{ left, top, right, bottom };
int hingeOrientation = env->GetIntField(JfoldableLayoutInfo, hingeOrientationId);
int state = env->GetIntField(JfoldableLayoutInfo, stateId);
int occlusionType = env->GetIntField(JfoldableLayoutInfo, occlusionTypeId);
bool isSeparating = env->GetBooleanField(JfoldableLayoutInfo, isSeparatingId);
// Store the values to an object for Unreal
result.Orientation = TEnumAsByte<EFoldOrientation>(hingeOrientation + 1);
result.State = TEnumAsByte<EFoldState>(state + 1);
result.OcclusionType = TEnumAsByte<EFoldOcclusionType>(occlusionType + 1);
result.IsSeparating = isSeparating;
// boundsRect
jobject boundsRect = env->GetObjectField(JfoldableLayoutInfo, boundsId);
left = env->GetIntField(boundsRect, leftId);
top = env->GetIntField(boundsRect, topId);
right = env->GetIntField(boundsRect, rightId);
bottom = env->GetIntField(boundsRect, bottomId);
// Store maxMetrics Rect to FVector4
result.FoldBounds = FIntVector4{ left, top, right, bottom };
-
Broadcast the result
via the OnLayoutChanged
delegate for use in the engine.
if (FFoldables_Helper::HasListener)
{
UE_LOG(LogTemp, Warning, TEXT("Broadcast"));
FFoldables_Helper::OnLayoutChanged.Broadcast(result);
}
}
Create a player controller and two UI states
This section focuses on adding a player controller and creating two user interface (UI) states for Flat and Flex modes. These objects are needed for the Flex mode logic implementation. Following are the steps to add a player controller and create two UI states :
-
Add a new Player Controller Blueprint. In Content Browser, go to Content > ThirdPersonCPP and right-click on Blueprints > Add/Import Content > Blueprint Class.
-
Pick Player Controller as its parent class.
-
Rename it as FlexPlayerController
.
NoteThe FlexPlayerController
added is generic and can be replaced by your custom player controller in an actual project.
-
Add a New C++ class with Actor Component as its parent class.
-
Name it as Foldables_Manager
and assign it to the Foldables_Tutorial
plugin. Click the Create Class button.
-
Open the FlexPlayerController
Blueprint by double-clicking it.
-
Click Open Full Blueprint Editor.
-
Attach the Actor Component to the FlexPlayerController
. In the left pane, click Add Component, then find and select the Foldables_Manager
.
-
Next, create a pair of UserWidget classes for the UI layouts needed: Flat mode UI for the full screen or Normal mode; and Flex mode UI for split-screen.
-
In Add C++ Class window, select the Show All Classes checkbox.
-
Find and pick UserWidget as the parent class. Then, click Next.
-
Name the new User Widget as FlatUI
and attach it to the plugin. Click Next.
-
Repeat the process but name the new User Widget as FlexUI
.
-
You might get an error when trying to compile stating that the UserWidget
is an undeclared symbol. To fix this, open the Foldables_Tutorial.Build.cs
file, and in the PublicDependencyModuleNames.AddRange
call, add "InputCore"
and "UMG"
to the list.
-
Create a pair of Blueprints made from subclasses of these two User Widgets.
-
Right-click on Content and create a New Folder called FoldUI.
-
Inside the newly created folder, right-click to add a new Blueprint Class.
-
In All Classes, choose FlatUI
and click the Select button.
-
Rename the Blueprint as BP_FlatUI
.
-
In the same folder, repeat the process but choose the FlexUI
class and rename the Blueprint as BP_FlexUI
.
-
Double-click on BP_FlatUI
and BP_FlexUI
, then design your two UIs like below to visualize switching between Flat and Flex mode:
NoteThis Code Lab activity does not cover the steps on how to create or design UI. If you want to learn about how to create your own game design in Unreal Engine 4, refer to Unreal Motion Graphics UI Designer guide.
Implement the Flex mode logic
After creating the FlexPlayerController
and the two UI states (BP_FlatUI
and BP_FlexUI
), you can now implement Flex mode logic in the Foldables_Manager
.
-
Open the Foldables_Manager.h
and include the necessary C++ header files:
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Engine.h"
#include "FlatUI.h"
#include "FlexUI.h"
#include "Foldables_Helper.h"
#include "Foldables_Manager.generated.h"
-
Remove the line below to save a little bit of performance as this Component
doesn't need to tick.
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
-
Set up the functions needed in Foldables_Manager
:
- The constructor, a function to create the UI Widgets
- The implementation of
OnLayoutChanged
delegate
public:
// Sets default values for this component's properties
UFoldables_Manager();
void CreateWidgets();
protected:
// Called when the game starts
virtual void BeginPlay() override;
protected:
void OnLayoutChanged(FFoldableLayoutInfo FoldableLayoutInfo);
-
Then, set up the variables needed:
- References to the Flat and Flex UI Classes
- References to the Flat and Flex UI Objects
-
Mark the pointers as UPROPERTY
to ensure that garbage collection does not delete the objects they point to.
TSubclassOf<UUserWidget> FlatUIClass;
TSubclassOf<UUserWidget> FlexUIClass;
UPROPERTY()
class UFlatUI* FlatUI;
UPROPERTY()
class UFlexUI* FlexUI;
-
Finally, define a new private function RestoreFlatMode()
, to disable Flex mode and return to Normal mode.
private:
void RestoreFlatMode();
-
Moving over to Foldables_Manager.cpp
, implement the constructor. Using the ConstructorHelpers
, find the UI classes and set the variables to store these classes.
-
Also, set the bCanEverTick
to false to prevent the component from ticking and remove the code block of TickComponent()
function.
// Sets default values for this component's properties
UFoldables_Manager::UFoldables_Manager()
{
PrimaryComponentTick.bCanEverTick = false;
static ConstructorHelpers::FClassFinder<UFlatUI> FlatUIBPClass(TEXT("/Game/FoldUI/BP_FlatUI"));
static ConstructorHelpers::FClassFinder<UFlexUI> FlexUIBPClass(TEXT("/Game/FoldUI/BP_FlexUI"));
if (FlatUIBPClass.Succeeded())
{
FlatUIClass = FlatUIBPClass.Class;
}
if (FlexUIBPClass.Succeeded())
{
FlexUIClass = FlexUIBPClass.Class;
}
}
-
Next, set up the BeginPlay()
function to link the delegate to the OnLayoutChanged()
function, to initialize the Foldables_Helper
, and to create the Widgets ready for use in the first frame.
// Called when the game starts
void UFoldables_Manager::BeginPlay()
{
Super::BeginPlay();
FFoldables_Helper::OnLayoutChanged.AddUObject(this, &UFoldables_Manager::OnLayoutChanged);
FFoldables_Helper::Init();
CreateWidgets();
}
-
Set up the CreateWidgets()
function to create the Widgets using the UI classes acquired in the constructor.
-
Add the FlatUI
Widget to the Viewport, assuming the app opens in Normal mode until it receives the FoldableLayoutInfo
.
void UFoldables_Manager::CreateWidgets()
{
FlatUI = CreateWidget<UFlatUI>((APlayerController*)GetOwner(), FlatUIClass, FName(TEXT("FlatUI")));
FlexUI = CreateWidget<UFlexUI>((APlayerController*)GetOwner(), FlexUIClass, FName(TEXT("FlexUI")));
FlatUI->AddToViewport();
}
-
Afterward, create the OnLayoutChanged()
function, which will be called via a delegate.
-
Inside this function, check whether the device’s folded state is HALF_OPENED
.
-
If so, check whether the orientation of the fold is HORIZONTAL
.
void UFoldables_Manager::OnLayoutChanged(FFoldableLayoutInfo FoldableLayoutInfo)
{
//If state is now Flex
if (FoldableLayoutInfo.State == EFoldState::HALF_OPENED)
{
if (FoldableLayoutInfo.Orientation == EFoldOrientation::HORIZONTAL)
{
NoteFor this Third Person Template, splitting the screen vertically isn’t ideal from a user experience (UX) point of view. For this Code Lab activity, split the screen only on the horizontal fold (top and bottom screen). If you want it vertically, you need to use the same principle in the next step but for the X-axis instead of the Y-axis. You must also ensure that you have a Flex UI object for the vertical layout.
-
If the device is both on Flex mode and horizontal fold, change the Viewport to only render on the top screen using the normalized position of the folding feature.
-
Then in an AsyncTask
on the game thread, disable the FlatUI
and enable the FlexUI
.
-
However, if the device is on Normal mode, then return to Flat UI using RestoreFlatMode()
function.
//Horizontal Split
float foldRatio = (float)FoldableLayoutInfo.FoldBounds.Y / (FoldableLayoutInfo.CurrentMetrics.W - FoldableLayoutInfo.CurrentMetrics.Y);
GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeX = 1.0f;
GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeY = foldRatio;
AsyncTask(ENamedThreads::GameThread, [=]()
{
if (FlatUI->IsInViewport())
{
FlatUI->RemoveFromParent();
}
if (!FlexUI->IsInViewport())
{
FlexUI->AddToViewport(0);
}
});
}
else
{
RestoreFlatMode();
}
}
else
{
RestoreFlatMode();
}
}
-
Reverse the Flex mode implementation logic to create the RestoreFlatMode()
function by setting the Viewport to fill the screen, then disable the FlexUI
and enable the FlatUI
.
void UFoldables_Manager::RestoreFlatMode()
{
GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeX = 1.0f;
GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeY = 1.0f;
AsyncTask(ENamedThreads::GameThread, [=]()
{
if (!FlatUI->IsInViewport())
{
FlatUI->AddToViewport(0);
}
if (FlexUI->IsInViewport())
{
FlexUI->RemoveFromParent();
}
});
}
Set up a Game Mode and attach the FlexPlayerController
The Game Mode defines the game rules, scoring, and any game-specific behavior. Set up the Game Mode in Unreal Editor by creating a Blueprint Class in the Content > ThirdPersonCPP > Blueprints folder.
Pick Game Mode Base as the parent class and rename it as FlexGameMode
.
Double-click on FlexGameMode
. In the drop-down menu next to Player Controller Class, choose the FlexPlayerController
.
Lastly, go to Edit > Project Settings > Project > Maps & Modes and select FlexGameMode
as the Default GameMode.
Build and run the app
Go to Edit > Package Project > Android to build the APK. Ensure that the Android development environment for Unreal is already set up to your computer.
NoteIf the build failed due to corrupted Build Tools, consider downgrading the version to 30 or lower using the SDK Manager. Or, simply rename d8.bat
to the name of the missing file (dx.bat
) in (SDK PATH) > build-tools > (version number) directory and, in lib folder of the same directory, rename d8.jar
to dx.jar
.
After packaging your Android project, run the game app on a foldable Galaxy device and see how the UI switches from Normal to Flex mode. If you don’t have any physical device, you can also test it on a Remote Test Lab device.
TipWatch this tutorial video and know how to easily test your app via Remote Test Lab.
You're done!
Congratulations! You have successfully achieved the goal of this Code Lab. Now, you can implement Flex mode in your Unreal Engine game app by yourself! If you're having trouble, you may download this file:
Flex Mode on Unreal Complete Code (20.16 MB)
To learn more, visit:
www.developer.samsung.com/galaxy-z
www.developer.samsung.com/galaxy-gamedev