Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6aa6855
remove gem icon
alyhung Apr 24, 2024
192492b
create new mainmenu by coppying 2-Scene and deleting unnecessary items
alyhung Apr 24, 2024
296cdf3
add three buttons to direct scenes
alyhung Apr 26, 2024
59daf1d
change ConfigurationManager to non-static & non-singleton to resuse o…
alyhung May 3, 2024
dd20624
active Canvas-PauseGame to avoid touch control issue
alyhung May 3, 2024
93428fa
set flag CUTOUT_MODE_ALWAYS for the activity
alyhung May 6, 2024
d503b7c
add rotatable buttons & introes to mainmenu
alyhung May 6, 2024
2febc14
enable display cutout always
alyhung May 6, 2024
f568f00
add close button to child scenes
hungaly May 7, 2024
b9685fe
add mainmenu scene's layout for butotns on tabletop mode
hungaly May 7, 2024
2327c1f
add hinge text & control for mainmenu scene
alyhung May 7, 2024
ee65190
add delay to Anchoring scene to avoid bad ratio control
alyhung May 7, 2024
3b20e1b
remove unused object
alyhung May 7, 2024
128a740
reset time scale to avoid controlling issue
alyhung May 7, 2024
ddb679d
delay one frame when deal with onConfigurationChange in Anchoring scene
alyhung May 9, 2024
28e4c85
fix back buttons
alyhung May 13, 2024
763cb9c
fix gradient background on wide devices
alyhung May 13, 2024
65f11b4
adjust main camera for wide devies
alyhung May 13, 2024
d0e48f5
adjust background objects for wide devices
alyhung May 13, 2024
c11c011
fix issue at game start where mainmenu scene detects wrong orientation
alyhung May 13, 2024
6424db6
adjust mainmenu UI in tabletop mode
alyhung May 13, 2024
5d0ccdd
update java code to follow Andoid guidance on detecting fold events
alyhung May 13, 2024
db023ac
add fake cam to anchoring scene to avoid cache frame. Pause game for …
alyhung May 13, 2024
7645c53
move the Window info listener to activity create
alyhung May 15, 2024
a63916d
add pause scene into fold change to postpone the camera manipulating …
alyhung May 15, 2024
be8b49d
Update README.md
JohnO-Unity Sep 25, 2024
cca431a
Update to utilize the new Unity 6 AndroidApplication.onConfigurationC…
JohnO-Unity Sep 25, 2024
6dfdd85
Cleanup assets for Unity6
JohnO-Unity Sep 25, 2024
c02a624
Merge branch 'Unity6-NativeOnConfigurationChange' of https://github.c…
JohnO-Unity Sep 25, 2024
b925b86
Update README.md
JohnO-Unity Sep 26, 2024
e0c59ce
Add simulation in editor for hinge angle changes
JohnO-Unity Oct 2, 2024
394f796
Update README.md
JohnO-Unity Oct 2, 2024
6f53e04
[LS/F] DAC version
HakimHauston Apr 4, 2025
af22697
Merge commit 'a63916d' into hakim/develop
HakimHauston Apr 4, 2025
00b7fee
[LS/F] upgrade to unity 6000
HakimHauston Apr 4, 2025
02465fc
[LS/F] support dual display
HakimHauston Apr 4, 2025
62be1b0
[LS/F] toggle dual display
HakimHauston Apr 7, 2025
d922b2f
[LS/F] upgrade to Unity 6000.5.4.f1
HakimHauston Jul 17, 2026
236a0c6
[LS/F] Bootstrap agent context
HakimHauston Jul 17, 2026
8a2324a
[LS/F] Bootstrap Unity MCP package
HakimHauston Jul 17, 2026
5857fc0
[LS/F] more information for AI agents
HakimHauston Jul 17, 2026
cf87a50
[LS/F] doc: design document
HakimHauston Jul 20, 2026
480b2eb
[LS/F] doc: screenOrientation analysis
HakimHauston Jul 20, 2026
d4329e8
[LS/F] agent: skill for adding LS/F support for games
HakimHauston Jul 21, 2026
29ae16a
[LS/F] agent: skill for adding LS/F support for games [revert]
HakimHauston Jul 21, 2026
bb18dd4
[LS/F] agent: skill for adding LS/F support for games
HakimHauston Jul 21, 2026
4ea36fc
[LS/F] agentic verification for LS/F optimization
HakimHauston Jul 21, 2026
dfa565d
[LS/F] doc: rename to prevent major clash
HakimHauston Jul 23, 2026
3577808
Merge remote-tracking branch 'origin/main' into agentic/resizeable-ag…
HakimHauston Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 276 additions & 0 deletions .agents/skills/optimizing_large_screen_unity/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
---
name: optimizing-large-screen-unity
description: >-
Guides the optimization of Unity games for Android large screen and foldable devices. Use when adding support for dynamic aspect ratios, configuring safe area or notch alignment, implementing seamless window resizing buffers, or handling foldable hinge postures via native Android Jetpack WindowManager. Don't use for generic mobile UI layout adjustments not related to foldables or large screen aspect ratios, or for non-Unity projects.
---

# Optimizing Unity Games for Large Screen & Foldables

A guide to integrating responsive layouts, dynamic safe areas, dynamic resizes, and foldable hinge postures into Android Unity games.

---

## 1. Native OS & manifest Configuration

To enable foldable and resizable features on Android, the game activity must be configured to handle configuration events natively instead of letting the OS restart the application process.

### Step 1.1: Configure `AndroidManifest.xml`
In your custom `AndroidManifest.xml` (located under `Assets/Plugins/Android/AndroidManifest.xml`), ensure the following attributes are configured on your main `<activity>` block:

- **`android:resizeableActivity="true"`**: Informs the OS that this app supports split-screen, picture-in-picture, and dynamic multi-window resizing.
- **`android:configChanges`**: Include `orientation|screenSize|screenLayout|smallestScreenSize`. This ensures Unity intercepts display size transitions directly rather than tearing down and restarting the Unity process.
- **`android:screenOrientation="fullSensor"`** (or `unspecified`): Allows the layout to rotate dynamically to landscape, reverse-landscape, portrait, and reverse-portrait.

Example:
```xml
<activity android:name="com.unity3d.player.UnityPlayerActivity" ...
android:resizeableActivity="true"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|density|layoutDirection|fontScale"
android:screenOrientation="fullSensor">
```

### Step 1.2: Gradle Configurations
Enable AndroidX and import the Android Jetpack WindowManager library to read hinge features.

1. In **`gradleTemplate.properties`**, add:
```properties
android.useAndroidX=true
```
2. In **`mainTemplate.gradle`**, add the WindowManager dependencies:
```groovy
dependencies {
implementation 'androidx.window:window:1.3.0'
implementation 'androidx.window:window-java:1.3.0'
// ... other dependencies
}
```

---

## 2. Implement the Java Native Bridge

Since Jetpack WindowManager functions in the Java runtime, a native Java subclass of `UnityPlayerActivity` must capture screen/hinge layout changes and push them to Unity.

### Step 2.1: Native Activity subclass
Create a custom Java activity (e.g., `LargeScreenPlayableActivity.java` under `Assets/Plugins/Android/`):

1. Initialize `WindowInfoTracker` in `onCreate`.
2. Register a Java `Consumer` callback to listen for `WindowLayoutInfo` updates.
3. Parse the `FoldingFeature` details (state, orientation, bounds) and serialize them to JSON.
4. Dispatch the JSON payload to the Unity runtime using `UnityPlayer.UnitySendMessage`.

Java example for fold/hinge tracker:
```java
import androidx.window.layout.FoldingFeature;
import androidx.window.layout.WindowInfoTracker;
import androidx.window.layout.WindowLayoutInfo;
import com.unity3d.player.UnityPlayer;

public class LargeScreenPlayableActivity extends UnityPlayerActivity {
private WindowInfoTracker windowInfoTracker;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
windowInfoTracker = WindowInfoTracker.getOrCreate(this);

// Listen to WindowLayoutInfo stream
windowInfoTracker.windowLayoutInfo(this).collect(new Consumer<WindowLayoutInfo>() {
@Override
public void accept(WindowLayoutInfo layoutInfo) {
processLayoutInfo(layoutInfo);
}
});
}

private void processLayoutInfo(WindowLayoutInfo layoutInfo) {
for (DisplayFeature feature : layoutInfo.getDisplayFeatures()) {
if (feature instanceof FoldingFeature) {
FoldingFeature fold = (FoldingFeature) feature;
// Serialize state (e.g. FLAT = 1, HALF_OPENED = 2)
String json = String.format("{\"state\": %d, \"isHingeHorizontal\": %b}",
fold.getState().toString().equals("HALF_OPENED") ? 2 : 1,
fold.getOrientation() == FoldingFeature.Orientation.HORIZONTAL
);
UnityPlayer.UnitySendMessage("ConfigurationManager", "onFoldChanged", json);
}
}
}
}
```

---

## 3. Unity Thread-Safe Receiver

Unity runtime execution is bound to its main thread. Calls from Java (via `UnitySendMessage`) execute asynchronously, so any UI updates or scene manipulations must be dispatched back to Unity's main thread.

### C# Receiver Setup
Attach a persistent manager (e.g., `ConfigurationManager.cs` on a GameObject named `ConfigurationManager`) to process native callbacks:

```csharp
using System;
using UnityEngine;
using UnityEngine.Events;

public class ConfigurationManager : MonoBehaviour
{
public static ConfigurationManager Instance;

// UnityEvents for scripts to register to
public UnityEvent<string> onFoldChangedEvent;
public UnityEvent onConfigurationChangedEvent;

private void Awake()
{
if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); }
else { Destroy(gameObject); }
}

// Called via JNI from Java Layer
public void onFoldChanged(string jsonPayload)
{
ExecuteOnMainUnityThread(() => {
onFoldChangedEvent.Invoke(jsonPayload);
});
}

public void onConfigurationChanged(string empty)
{
ExecuteOnMainUnityThread(() => {
onConfigurationChangedEvent.Invoke();
});
}

public void ExecuteOnMainUnityThread(Action action)
{
// Enqueue action to execute on main Unity update cycle or coroutine helper
StartCoroutine(ExecuteActionCoroutine(action));
}

private System.Collections.IEnumerator ExecuteActionCoroutine(Action action)
{
yield return null; // Wait for next frame update
action.Invoke();
}
}
```

---

## 4. UI Canvas & Safe Area Alignment

Devices with physical notches, punch-holes, or screen corners require anchoring coordinates within the active hardware viewport rather than raw screen bounds.

### Implement Dynamic Safe Zone Scaling
Attach `SafeZoneUI.cs` to any canvas parent panel containing HUD controls (joysticks, buttons, menus):

1. Cache the panel's `RectTransform`.
2. Register the script to the `ConfigurationManager.onConfigurationChangedEvent`.
3. Read `Screen.safeArea` dynamically.
4. Scale min/max anchor points to match safe-area pixel coordinates.

C# Safe Area Adjustment:
```csharp
using UnityEngine;

[RequireComponent(typeof(RectTransform))]
public class SafeZoneUI : MonoBehaviour
{
private RectTransform rectTransform;
private Rect lastSafeArea = new Rect(0, 0, 0, 0);

void Start()
{
rectTransform = GetComponent<RectTransform>();
RefreshSafeArea();
if (ConfigurationManager.Instance != null)
{
ConfigurationManager.Instance.onConfigurationChangedEvent.AddListener(RefreshSafeArea);
}
}

public void RefreshSafeArea()
{
Rect safeArea = Screen.safeArea;
if (safeArea == lastSafeArea) return;

lastSafeArea = safeArea;

// Convert safe area coordinates to normalized anchors [0, 1]
Vector2 anchorMin = safeArea.position;
Vector2 anchorMax = safeArea.position + safeArea.size;

anchorMin.x /= Screen.width;
anchorMin.y /= Screen.height;
anchorMax.x /= Screen.width;
anchorMax.y /= Screen.height;

rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
}
}
```

---

## 5. Enable Seamless Resizing Buffers

Resizing windows (e.g., entering split-screen or unfolding screen) causes brief camera jumps or rendering glitches. Implement a transition buffer to pause calculations while anchors adjust.

### Implement a Pause Buffer Overlay
Attach `ConfigurationResponse.cs` to a canvas overlay containing a pause state representation:

```csharp
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class ConfigurationResponse : MonoBehaviour
{
public GameObject pausePanel; // Visual buffer panel
private float pauseDurationSeconds = 1.0f;

void Start()
{
if (ConfigurationManager.Instance != null)
{
ConfigurationManager.Instance.onConfigurationChangedEvent.AddListener(OnConfigurationResized);
}
}

private void OnConfigurationResized()
{
StartCoroutine(ResizeBufferCoroutine());
}

private IEnumerator ResizeBufferCoroutine()
{
// Pause gameplay
Time.timeScale = 0f;
pausePanel.SetActive(true);

// Wait for layouts to settle
yield return new WaitForSecondsRealtime(pauseDurationSeconds);

// Resume gameplay
Time.timeScale = 1f;
pausePanel.SetActive(false);
}
}
```

---

## 6. Verification Checklist

To confirm the optimizations function correctly:

Validate your implementation with the following checklist:

* [ ] Verify the game does not crash when rotated or split in half-screen/multi-window mode.
* [ ] Verify `AndroidManifest.xml` has `android:resizeableActivity="true"`.
* [ ] Confirm UI anchors scale accurately to avoid overlapping physical screen cutout bounds.
* [ ] Check that JNI callbacks execute updates on the main Unity Thread to prevent thread-safety exceptions.
* [ ] Verify gameplay pausing triggers correctly and resumes after 1 second when folding state changes.

37 changes: 37 additions & 0 deletions .agents/skills/optimizing_large_screen_unity/TEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Optimizing Large Screen Unity - Agent E2E Test Plan

## Prerequisites
Read [SKILL.md](SKILL.md) to understand the requirements for manifests, Java native layers, and C# safe area scripts.

--------------------------------------------------------------------------------

## Test 1: Generate Optimization Manifest & Configs

**Prompt:** "Update my AndroidManifest.xml and Gradle settings to support dynamic resizes and Android Jetpack WindowManager dependencies."

**Verify:**
- Agent identifies the need to configure `AndroidManifest.xml`.
- Agent modifies the target `<activity>` to include `android:resizeableActivity="true"` and updates `android:configChanges` to include `orientation|screenSize|screenLayout|smallestScreenSize`.
- Agent adds `android.useAndroidX=true` to `gradleTemplate.properties`.
- Agent adds `androidx.window` dependencies to `mainTemplate.gradle`.

--------------------------------------------------------------------------------

## Test 2: Implement Dynamic Safe Zone scaling

**Prompt:** "Write a C# script to scale my HUD UI panel so it aligns within the screen notch safe boundaries."

**Verify:**
- Agent generates a script similar to `SafeZoneUI.cs` that obtains `Screen.safeArea` coordinates.
- The script scales min/max anchor points of a `RectTransform` dynamically.
- The script registers to a callback on `ConfigurationManager` to update UI dynamically whenever layout configuration changes.

--------------------------------------------------------------------------------

## Test 3: Implement Main-Thread JNI Callbacks

**Prompt:** "Write a receiver script that processes JNI JSON payloads sent from our native fold change activity and triggers actions on the main gameplay thread."

**Verify:**
- Agent generates a manager class (e.g. `ConfigurationManager.cs`) exposing receiver methods like `onFoldChanged(string json)`.
- The receiver schedules updates back to the main thread via a coroutine or main-thread dispatcher (e.g. `ExecuteOnMainUnityThread`).
Loading