Add XR sample assets and update settings
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
using UnityEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// A static class that initializes a test surface on Android
|
||||
/// using Java Native Interface (JNI) calls. It facilitates interaction between Unity and
|
||||
/// Android native code, particularly for handling texture and surface operations.
|
||||
/// </summary>
|
||||
public static class AndroidTestSurface
|
||||
{
|
||||
private static System.IntPtr? _TestSurfaceClass;
|
||||
private static System.IntPtr initTestSurfaceMethodId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reference to the Java class 'TestSurface'. It uses JNI to find and hold a
|
||||
/// global reference to the class for future use.
|
||||
/// </summary>
|
||||
private static System.IntPtr TestSurfaceClass
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_TestSurfaceClass.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find the Java class and create a global reference
|
||||
System.IntPtr testSurfaceClassLocal = AndroidJNI.FindClass("com/unity/xr/compositorlayers/TestSurface");
|
||||
|
||||
if (testSurfaceClassLocal != System.IntPtr.Zero)
|
||||
{
|
||||
_TestSurfaceClass = AndroidJNI.NewGlobalRef(testSurfaceClassLocal);
|
||||
AndroidJNI.DeleteLocalRef(testSurfaceClassLocal);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to find Java class `TestSurface`");
|
||||
_TestSurfaceClass = System.IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError("Exception occurred while finding Java class `TestSurface`");
|
||||
Debug.LogException(ex);
|
||||
_TestSurfaceClass = System.IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
return _TestSurfaceClass.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a test surface using a JNI call to the 'InitTestSurface' method of the
|
||||
/// 'TestSurface' Java class. It passes a native Android object and a texture converted to
|
||||
/// a Bitmap to the Java method.
|
||||
/// </summary>
|
||||
/// <param name="jobject">The native Android object to which the surface is attached.</param>
|
||||
/// <param name="texture">The Unity Texture2D to be converted and passed as a Bitmap.</param>
|
||||
public static void InitTestSurface(System.IntPtr jobject, Texture2D texture)
|
||||
{
|
||||
if (initTestSurfaceMethodId == System.IntPtr.Zero)
|
||||
{
|
||||
// Retrieve the method ID for 'InitTestSurface'
|
||||
initTestSurfaceMethodId = AndroidJNI.GetStaticMethodID(TestSurfaceClass, "InitTestSurface", "(Ljava/lang/Object;Landroid/graphics/Bitmap;)V");
|
||||
}
|
||||
|
||||
// Convert the texture to a Bitmap and call the Java method
|
||||
using (AndroidJavaObject bitmap = Texture2DToBitmap(texture))
|
||||
{
|
||||
System.IntPtr bitmapPtr = bitmap.GetRawObject();
|
||||
jvalue[] args = new jvalue[2];
|
||||
args[0] = new jvalue { l = jobject };
|
||||
args[1] = new jvalue { l = bitmapPtr };
|
||||
|
||||
AndroidJNI.CallStaticVoidMethod(TestSurfaceClass, initTestSurfaceMethodId, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Unity Texture2D to an Android Bitmap object. This is used to pass textures
|
||||
/// from Unity to Android native code.
|
||||
/// </summary>
|
||||
/// <param name="texture">The Unity Texture2D to be converted.</param>
|
||||
/// <returns>An AndroidJavaObject representing the Bitmap.</returns>
|
||||
private static AndroidJavaObject Texture2DToBitmap(Texture2D texture)
|
||||
{
|
||||
byte[] imageBytes = texture.EncodeToPNG();
|
||||
sbyte[] signedImageBytes = (sbyte[])(System.Array)imageBytes;
|
||||
|
||||
using (AndroidJavaClass byteBufferClass = new AndroidJavaClass("java.nio.ByteBuffer"))
|
||||
{
|
||||
AndroidJavaObject byteBuffer = byteBufferClass.CallStatic<AndroidJavaObject>("wrap", signedImageBytes);
|
||||
using (AndroidJavaClass bitmapFactory = new AndroidJavaClass("android.graphics.BitmapFactory"))
|
||||
{
|
||||
AndroidJavaObject bitmap = bitmapFactory.CallStatic<AndroidJavaObject>("decodeByteArray", byteBuffer.Call<sbyte[]>("array"), 0, signedImageBytes.Length);
|
||||
|
||||
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3)
|
||||
{
|
||||
using (var matrix = new AndroidJavaObject("android.graphics.Matrix"))
|
||||
{
|
||||
matrix.Call<bool>("preScale", 1.0f, -1.0f);
|
||||
|
||||
using (var bitmapClass = new AndroidJavaClass("android.graphics.Bitmap"))
|
||||
{
|
||||
AndroidJavaObject flippedBitmap = bitmapClass.CallStatic<AndroidJavaObject>("createBitmap",
|
||||
bitmap, 0, 0, bitmap.Call<int>("getWidth"), bitmap.Call<int>("getHeight"), matrix, false);
|
||||
return flippedBitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 907440e96477d434b8027232e72b4b33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac2dd0c0c645be04ba9fa58a6af05431
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A class that ensures that the Android minimum SDK version meets certain requirements.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public class AndroidBuildSettings : IPreprocessBuildWithReport
|
||||
{
|
||||
/// Specifies the order in which this pre-build process should be executed.
|
||||
public int callbackOrder { get { return 0; } }
|
||||
|
||||
/// Static constructor called on load to initially set Android build settings.
|
||||
static AndroidBuildSettings()
|
||||
{
|
||||
SetAndroidBuildSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before the build process begins. Ensures Android build settings are correctly set.
|
||||
/// </summary>
|
||||
/// <param name="report">Contains information about the build, such as its target platform and output path.</param>
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
SetAndroidBuildSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum SDK version for Android builds to a specified level if it is currently set lower.
|
||||
/// This ensures compatibility with features provided in composition layers.
|
||||
/// </summary>
|
||||
private static void SetAndroidBuildSettings()
|
||||
{
|
||||
if (PlayerSettings.Android.minSdkVersion < AndroidSdkVersions.AndroidApiLevel24)
|
||||
{
|
||||
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel24;
|
||||
Debug.Log("Android minimum SDK version has been updated to Level 24. The lowest level supported by the composition layers package.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66d508d62ebbcb742b8674838d4b2a11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
#if UNITY_EDITOR && UNITY_ANDROID
|
||||
using UnityEditor;
|
||||
using UnityEditor.Android;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This class primarily focuses on copying specific files
|
||||
/// from a source folder to the Android project's assets directory after the Gradle project
|
||||
/// has been generated.
|
||||
/// </summary>
|
||||
public class UploadAndroidFiles : IPostGenerateGradleAndroidProject
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the order in which this post-processing task should be executed.
|
||||
/// </summary>
|
||||
public int callbackOrder => 1;
|
||||
|
||||
/// <summary>
|
||||
/// Called after the Gradle Android project is generated. Copies files from a specified
|
||||
/// source folder to the project's assets directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the generated Gradle project.</param>
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
|
||||
string sourceFolder = Path.Combine(Application.dataPath, "Samples/XR Composition Layers/2.2.0/Sample External Android Surface Project/StreamingAssets");
|
||||
string destinationFolder = Path.Combine(path, "src/main/assets");
|
||||
|
||||
// Create the destination folder if it does not exist
|
||||
if (!Directory.Exists(destinationFolder))
|
||||
{
|
||||
Directory.CreateDirectory(destinationFolder);
|
||||
}
|
||||
|
||||
CopyFilesRecursively(sourceFolder, destinationFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all files and subdirectories from a source path to a target path recursively.
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">The source directory path.</param>
|
||||
/// <param name="targetPath">The target directory path where files should be copied.</param>
|
||||
private static void CopyFilesRecursively(string sourcePath, string targetPath)
|
||||
{
|
||||
// Copy each file in the source directory to the target directory
|
||||
foreach (string filePath in Directory.GetFiles(sourcePath))
|
||||
{
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
string destFile = Path.Combine(targetPath, fileName);
|
||||
File.Copy(filePath, destFile, true);
|
||||
}
|
||||
|
||||
// Recursively copy each subdirectory
|
||||
foreach (string subdirectoryPath in Directory.GetDirectories(sourcePath))
|
||||
{
|
||||
string subdirectoryName = Path.GetFileName(subdirectoryPath);
|
||||
string destSubdirectory = Path.Combine(targetPath, subdirectoryName);
|
||||
if (!Directory.Exists(destSubdirectory))
|
||||
{
|
||||
Directory.CreateDirectory(destSubdirectory);
|
||||
}
|
||||
CopyFilesRecursively(subdirectoryPath, destSubdirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c6257f7aad5b4445ad10809fcc8f816
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using Unity.XR.CompositionLayers;
|
||||
#if UNITY_XR_OPENXR_COMPLAYER
|
||||
using UnityEngine.XR.OpenXR.CompositionLayers;
|
||||
#endif
|
||||
using UnityEngine.Networking;
|
||||
|
||||
/// <summary>
|
||||
/// Class for loading and displaying an image on Android devices.
|
||||
/// </summary>
|
||||
public class TestAndroidImage : MonoBehaviour
|
||||
{
|
||||
public string ImageName;
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine started upon script activation to initiate image loading.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerator for coroutine management.</returns>
|
||||
private IEnumerator Start()
|
||||
{
|
||||
yield return StartCoroutine(LoadImage());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main coroutine handling the image loading process based on the platform.
|
||||
/// Loads an image using specified ImageName and displays it if found.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerator for coroutine management.</returns>
|
||||
private IEnumerator LoadImage()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ImageName))
|
||||
{
|
||||
Texture2D image = null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Load image for Unity Editor
|
||||
image = LoadImageForEditor();
|
||||
#elif UNITY_ANDROID
|
||||
// Load image for Android
|
||||
yield return StartCoroutine(LoadImageForAndroid(loadedImage => image = loadedImage));
|
||||
#endif
|
||||
if (image != null)
|
||||
{
|
||||
// Display the loaded image
|
||||
yield return DisplayImage(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Image not found at path: " + ImageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Loads an image from the project's assets when running in the Unity Editor.
|
||||
/// </summary>
|
||||
/// <returns>Texture2D - The loaded image texture.</returns>
|
||||
private Texture2D LoadImageForEditor()
|
||||
{
|
||||
var guids = UnityEditor.AssetDatabase.FindAssets(Path.GetFileNameWithoutExtension(ImageName));
|
||||
if (guids.Length > 0)
|
||||
{
|
||||
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
return UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#elif UNITY_ANDROID
|
||||
/// <summary>
|
||||
/// Coroutine for loading an image in an Android build. Uses UnityWebRequest to fetch the image.
|
||||
/// </summary>
|
||||
/// <param name="onLoaded">Callback action to handle the loaded Texture2D.</param>
|
||||
/// <returns>IEnumerator for coroutine management.</returns>
|
||||
private IEnumerator LoadImageForAndroid(Action<Texture2D> onLoaded)
|
||||
{
|
||||
string filePath = Path.Combine(Application.streamingAssetsPath, ImageName);
|
||||
using (UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(filePath))
|
||||
{
|
||||
yield return uwr.SendWebRequest();
|
||||
if (uwr.result == UnityWebRequest.Result.ConnectionError || uwr.result == UnityWebRequest.Result.ProtocolError)
|
||||
{
|
||||
Debug.LogError("Error while loading image: " + uwr.error);
|
||||
onLoaded(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
onLoaded(DownloadHandlerTexture.GetContent(uwr));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine handling the display of a loaded image using XR composition layers.
|
||||
/// Waits for the surface from OpenXRLayerUtility before displaying the image.
|
||||
/// </summary>
|
||||
/// <param name="image">The Texture2D image to be displayed.</param>
|
||||
/// <returns>IEnumerator for coroutine management.</returns>
|
||||
private IEnumerator DisplayImage(Texture2D image)
|
||||
{
|
||||
if (image != null)
|
||||
{
|
||||
CompositionLayer layer = gameObject.GetComponent<CompositionLayer>();
|
||||
IntPtr surface = IntPtr.Zero;
|
||||
yield return new WaitUntil(() =>
|
||||
{
|
||||
#if UNITY_XR_OPENXR_COMPLAYER
|
||||
surface = OpenXRLayerUtility.GetLayerAndroidSurfaceObject(layer.GetInstanceID());
|
||||
#endif
|
||||
return (surface != IntPtr.Zero);
|
||||
});
|
||||
|
||||
AndroidTestSurface.InitTestSurface(surface, image);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No image provided.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler triggered when the application gains or loses focus.
|
||||
/// Restarts image loading process when the application gains focus.
|
||||
/// </summary>
|
||||
/// <param name="hasFocus">Boolean indicating if the application has focus.</param>
|
||||
void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
if (hasFocus)
|
||||
{
|
||||
StartCoroutine(LoadImage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1537674bf1cc874f9b390067e444367
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user