需求背景
需求方希望在VR应用中实现相机拍照打卡功能,相机所拍摄的照片需要保存在PICO设备中,而且能够被PICO设备的【文件管理】-【图片】窗口中自动刷新显示出来。
难点分析
拍照+保存的功能已经实现了,但是PICO设备的【文件管理】-【图片】视图中无论如何也不能自动刷新出来。因此需要调用java方法来刷新图片文件。
实现步骤
- 先写一个能够实现刷新图片的功能的java脚本
- 使用AndroidStudio,构建jar包
- 在Unity C#脚本里,调用java方法
实现步骤详解
1. 先写一个能够实现刷新图片的功能的java脚本
package com.[MyCompanyName].mediascanner;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.util.Log;
public class MediaScannerHelper {
public static void scanFile(Context context, String path) {
MediaScannerConnection.scanFile(context,
new String[] { path }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("TAG", "Finished scanning " + path);
}
});
}
}
2. 使用AndroidStudio,构建jar包
下载并安装AndroidStudio,安装时一直点击[下一步]即可。创建一个None Activity空工程,然后在左侧Project窗口中的根节点右键 - New - Module,起一个Module名,找到[Module] - src - main - java - com - [MyCompanyName] - [Module]节点,右键新建一个java脚本,并补充java代码。单击Module,点击AndroidStudio工具栏的Build - MakeModuleXXX(或Make Sellected Modules),去到AndroidStudio工程目录,文件搜索*.jar
并找到最新构建的jar包(新安装的AndroidStudio默认自动命名为Class.jar),用zip工具打开jar文件,在zip中直接右键删除jar包中的androidx
目录以及com/google
目录。将jar文件重命名后,拖至Unity工程的Assets/Plugins/Android
目录中。
3. 在Unity C#脚本里,调用java方法
public void ScanFile(string path)
{
AndroidJavaClass contextClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject context = contextClass.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaClass mediaScannerHelperClass = new AndroidJavaClass("com.[MyCompanyName].mediascanner.MediaScannerHelper");
mediaScannerHelperClass.CallStatic("scanFile", context, path);
}
经过上面的步骤,当玩家使用相机道具进行拍照并将照片保存到本地后,PICO4设备就可以自动在【文件管理】-【图片】窗口中刷新出来刚刚的照片了。拍照保存功能可参考如下代码:
public void StartCaptureScreenshot()
{
StartCoroutine(DoCaptureScreenshot());
}
IEnumerator DoCaptureScreenshot()
{
yield return new WaitForEndOfFrame();
// Store camera's RenderTexture
realtimeRenderTexture = currCamera.targetTexture;
// Render from camera
currCamera.targetTexture = screenshotRenderTexture;
currCamera.Render();
RenderTexture.active = screenshotRenderTexture;
// Read texture
Texture2D screenshotTex = new Texture2D(TEX_WIDTH, TEX_HEIGHT, TextureFormat.RGB24, false);
screenshotTex.ReadPixels(new Rect(0, 0, TEX_WIDTH, TEX_HEIGHT), 0, 0);
screenshotTex.Apply();
// Reset
currCamera.targetTexture = realtimeRenderTexture;
RenderTexture.active = null;
// Store png
byte[] bytes = screenshotTex.EncodeToPNG();
string formattedTime = DateTime.Now.ToString("yyMMddHHmmss");
#if !UNITY_EDITOR
var rootPath = "/storage/emulated/0/";
var filename = rootPath + "Screenshot_" + formattedTime + ".png";
File.WriteAllBytes(filename, bytes);
ScanFile(filename);
#endif
}
其中,这段C#代码中所调用的ScanFile
方法,就是前面用来调用java方法的代码段。
a. 以上内容中所有的
[MyCompanyName]
是需要替换成自己的公司名或项目名的。
b. 最后C#代码中,SAVE_SCREENSHOT_TO_PERSISTANT_DATA_PATH、SAVE_SCREENSHOT_TO_PICO_SCREENSHOT_PATH、SAVE_SCREENSHOT_TO_PICO_ROOT_PATH三个宏定义看需求而定,一般只在C#脚本头部开启其中一个保存路径即可。
【补充】性能优化
1. 使用隔帧渲染
问题背景:由于场景中存在第二个摄像机,场景运行时帧率较低
思路:将场景中的这个摄像机,进行隔帧渲染处理
解决方案:如果需要实现每过几帧才渲染一次的效果,可以考虑使用脚本来实现。例如,可以编写一个脚本,使其每隔X帧渲染一次,即在脚本中通过修改摄像机的"enabled"属性来控制是否渲染。
2. 使用异步IO存储
问题背景:画面保存的时候,总会卡顿一下,帧率变低
思路:由于采用了同步IO,因此IO在存储数据的时候,会阻塞主线程,可考虑改用异步IO的方式
解决方案:主要是将 EncodeToPNG() 和 WriteAllBytesAsync() 放到 Task.Run( () => { } ) 中
IEnumerator DoCaptureScreenshot()
{
yield return new WaitForEndOfFrame();
// Store camera's RenderTexture
realtimeRenderTexture = currCamera.targetTexture;
// Render from camera
currCamera.targetTexture = screenshotRenderTexture;
currCamera.Render();
RenderTexture.active = screenshotRenderTexture;
RemainImageOnScreen(duration: photoRemainTime);
var filename = "";
var customPath = "/storage/emulated/0/MyStoredPhotos/";
var formattedTime = DateTime.Now.ToString("yyMMddHHmmss");
// Read texture
Texture2D screenshotTex = new Texture2D(TEX_WIDTH, TEX_HEIGHT, TextureFormat.RGB24, false);
screenshotTex.ReadPixels(new Rect(0, 0, TEX_WIDTH, TEX_HEIGHT), 0, 0);
screenshotTex.Apply();
// Reset
currCamera.targetTexture = realtimeRenderTexture;
RenderTexture.active = null;
Task.Run(() =>
{
// Store png
byte[] bytes = screenshotTex.EncodeToPNG();
if (!Directory.Exists(customPath))
{
Directory.CreateDirectory(customPath);
}
filename = customPath + "MyScreenshot_" + formattedTime + ".png";
File.WriteAllBytesAsync(filename, bytes);
});
yield return new WaitForSeconds(0.5f);
ScanFile(filename);
}