1. Take photos
This can be achieved using Unity’s RenderTexture and Texture2D classes, which require a camera to take pictures, and then save the camera’s rendering results to the Texture by creating a RenderTexture. Finally convert this Texture to a regular 2D texture and save it as an image file.
public class PhotoShootHandler : MonoBehaviour { [Tooltip("Camera used to render the scene. If not set, the main camera will be used.")] public Camera foregroundCamera; [Tooltip("Array of sprite transforms that will be used for displaying the countdown until image shot.")] public Transform[] countdown; [Tooltip("UI-Text used to display information messages.")] public UnityEngine.UI.Text infoText; private IEnumerator currentRoutine = null,currentPhotoRoutine = null; /// <summary> /// Check if taking pictures /// </summary> public bool IsInProgress() { return (currentRoutine != null); } /// <summary> /// Start taking pictures /// </summary> public void CountdownAndMakePhoto() { if (currentRoutine != null) return; currentRoutine = CoCountdownAndMakePhoto(); StartCoroutine(currentRoutine); } private IEnumerator CoCountdownAndMakePhoto() { if (countdown != null & amp; & amp; countdown.Length > 0) { for (int i = 0; i < countdown.Length; i + + ) { if (countdown[i]) countdown[i].gameObject.SetActive(true); yield return new WaitForSeconds(1f); if (countdown[i]) countdown[i].gameObject.SetActive(false); } } MakePhoto(); yield return null; currentRoutine = null; } public void MakePhoto() { MakePhoto(true); } public string MakePhoto(bool openIt) { int resWidth = Screen.width; int resHeight = Screen.height; Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false); RenderTexture rt = new RenderTexture(resWidth, resHeight, 24); if(infoText) { infoText.text = string.Empty; } if (!foregroundCamera) { foregroundCamera = Camera.main; } // render if (foregroundCamera & amp; & amp; foregroundCamera.enabled) { foregroundCamera.targetTexture = rt; foregroundCamera.Render(); foregroundCamera.targetTexture = null; } //Get the picture, RenderTexture.active is currently the active rendering texture RenderTexture prevActiveTex = RenderTexture.active; RenderTexture.active = rt; screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0); //reduction RenderTexture.active = prevActiveTex; Destroy(rt); //byte[] btScreenShot = screenShot.EncodeToJPG(); byte[] btScreenShot = screenShot.EncodeToPNG(); Destroy(screenShot); // save string sDirName = Application.persistentDataPath + "/Screenshots"; if (!Directory.Exists(sDirName)) Directory.CreateDirectory(sDirName); string fileName = string.Format("{0:F0}", Time.realtimeSinceStartup * 10f) + ".png"; string sFileName = sDirName + "/" + fileName; File.WriteAllBytes(sFileName, btScreenShot); Debug.Log("Photo saved to: " + sFileName); if(infoText) { infoText.text = "Saved to: " + sFileName; } //Open i file for display /*if (openIt) { System.Diagnostics.Process.Start(sFileName); }*/ //upload photos UploadRecordPhoto(fileName); return sFileName; } /// <summary> /// Upload screen recording pictures /// </summary> /// <returns></returns> private void UploadRecordPhoto(string fileName) { if (currentPhotoRoutine != null) return; currentPhotoRoutine = DoUploadRecordVideo(fileName); StartCoroutine(currentPhotoRoutine); } private IEnumerator DoUploadRecordVideo(string fileName) { yield return new WaitForSeconds(1.0f); string filePath = Application.persistentDataPath + "/Screenshots" + "/" + fileName; if (!IsOccupied(filePath, "Upload Photo")) { print("Press photo file"); HttpUploadHandler.Instance.PushImageInQueue(fileName); currentPhotoRoutine = null; } else { currentPhotoRoutine = null; UploadRecordPhoto(fileName); } } #region tools /// <summary> /// Determine whether the file is occupied /// </summary> /// <param name="filePath"></param> /// <returns></returns> public bool IsOccupied(string filePath, string fileFrom) { FileStream stream = null; try { stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); return false; } catch { print(fileFrom + "File is occupied"); return true; } finally { if (stream != null) { stream.Close(); } } } #endregion }
2. Record current interface video (applicable to PC and mobile terminals)
1. Overview:
Unity can use Unity Recorder to record videos, but it can only be used in the editor. Unity scenes and animations can be recorded in running mode.
Timeline is recorded as animation or video.
For non-editor conditions, we are currently exploring the use of ffmpeg to implement it, and it works normally.
2. ffmpeg parameter description
* -f: format
* gdigrab: ffmpeg’s built-in method for grabbing the Windows desktop, supports grabbing windows with specified names
* dshow: relies on the third-party software Screen Capture Recorder (hereinafter referred to as SCR)
* -i: input source
* title: The name of the window to be recorded, only used in GDIGRAB mode
* video: video playback hardware name or “screen-capture-recorder”, the latter relies on SCR
* audio: audio playback hardware name or “virtual-audio-capturer”, the latter relies on SCR
* -preset ultrafast: Encode at the fastest speed, resulting in large video files
* -c:v: video encoding method
* -c:a: audio encoding method
* -b:v: video bitrate
* -r: video frame rate
* -s: video resolution
* -y: The output file overwrites the existing file without prompting
*
*FFMPEG official document:
ffmpeg Documentation
* Screen Capture Recorder home page:
https://github.com/rdp/screen-capture-recorder-to-video-windows-free
3. Implementation code
using Newtonsoft.Json.Serialization; using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using UnityEngine; [ExecuteInEditMode] public class VideoRecoderHandler : MonoBehaviour { #region DLL needed to simulate console signals [DllImport("kernel32.dll")] static extern bool GenerateConsoleCtrlEvent(int dwCtrlEvent, int dwProcessGroupId); [DllImport("kernel32.dll")] static extern bool SetConsoleCtrlHandler(IntPtr handlerRoutine, bool add); [DllImport("kernel32.dll")] static extern bool AttachConsole(int dwProcessId); [DllImport("kernel32.dll")] static extern bool FreeConsole(); #endregion #region settings menu public enum RecordType { GDIGRAB, DSHOW } public enum Bitrate { _1000k, _1500k, _2000k, _2500k, _3000k, _3500k, _4000k, _5000k, _8000k } public enum Framerate { _14, _twenty four, _30, _45, _60 } public enum Resolution { _1280x720, _1920x1080, _1080x1920, _Auto } public enum OutputPath { Desktop, StreamingAsset, DataPath, PersistentDataPath, Custom } #endregion #region members [Tooltip("If Debug is enabled, the CMD window will be displayed, otherwise it will not be displayed.")] [SerializeField] private bool _debug = false; [Tooltip("DSHOW - Record full screen \ GUIGRAB - Record game window (release version only)")] public RecordType recordType = RecordType.DSHOW; public Resolution resolution = Resolution._1280x720; public Framerate framerate = Framerate._24; public Bitrate bitrate = Bitrate._1500k; public OutputPath outputPath = OutputPath.Desktop; public string customOutputPath = @"D:/Records"; public bool IsRecording { get { return _isRecording; } } // Parameters: window name -b bitrate -r frame rate -s resolution file path file name private const string FFARGS_GDIGRAB = "-f gdigrab -i title={0} -f dshow -i audio="virtual-audio-capturer" -y -preset ultrafast -rtbufsize 3500k -pix_fmt yuv420p -b:v {1} -r {2} -s {3} {4}/{5}.mp4"; //private const string FFARGS_GDIGRAB = "-f gdigrab -i title={0} -f dshow -i audio="virtual-audio-capturer" -f dshow -i video="screen-capture-recorder" -y -preset ultrafast -rtbufsize 3500k -pix_fmt yuv420p -b:v {1} -r {2} -s {3} {4}/{5}.mp4"; // Parameters: -b bitrate -r frame rate -s resolution file path file name private const string FFARGS_DSHOW = "-f dshow -i video="screen-capture-recorder" -f dshow -i audio="virtual-audio-capturer" -y -preset ultrafast -rtbufsize 3500k -b:v {0} -r {1} -s {2} {3}/{4}.mp4"; private string _ffpath; private string _ffargs; private int _pid; private bool _isRecording = false; private string curVideoName; private IEnumerator currentVideoRoutine = null; #endregion #if !UNITY_EDITOR & amp; & amp; !DEVELOPMENT_BUILD private void Start() { _debug = false; } #endif #if UNITY_EDITOR || DEVELOPMENT_BUILD private void OnGUI() { /*if (GUILayout.Button("Start")) StartRecording(); if (GUILayout.Button("Stop")) StopRecording(() => { UnityEngine.Debug.Log("End recording."); });*/ } #endif #if UNITY_EDITOR private void OnValidate() { if (_debug) UnityEngine.Debug.Log("FFRecorder - CMD window is enabled."); else UnityEngine.Debug.Log("FFRecorder - CMD window is disabled."); if (recordType == RecordType.GDIGRAB) { UnityEngine.Debug.Log("FFRecorder - Use [GDIGRAB] mode to record the current window."); UnityEngine.Debug.LogError("FFRecorder - [GDIGRAB] mode is not available in the editor."); } else if (recordType == RecordType.DSHOW) { UnityEngine.Debug.Log("FFRecorder - Use [DSHOW] mode to record full screen."); } } #endif public void StartRecording() { if (_isRecording) { UnityEngine.Debug.LogError("FFRecorder::StartRecording - There is currently a recording process."); return; } // Kill the existing ffmpeg process, do not add the .exe suffix Process[] goDie = Process.GetProcessesByName("ffmpeg"); foreach (Process p in goDie) p.Kill(); // Parse the settings, if the settings are correct, start recording bool validSettings = ParseSettings(); if(validSettings) { UnityEngine.Debug.Log("FFRecorder::StartRecording - Execute command: " + _ffpath + " " + _ffargs); StartCoroutine(IERecording()); } else { UnityEngine.Debug.LogError("FFRecorder::StartRecording - improper setting, recording canceled, please check the console output."); } } public void StopRecording(Action _OnStopRecording) { if (!_isRecording) { UnityEngine.Debug.LogError("FFRecorder::StopRecording - There is currently no recording process and the operation has been canceled."); return; } StartCoroutine(IEExitCmd(_OnStopRecording)); } public void FocusStopRecording(Action _OnStopRecording) { if (_isRecording) { try { UnityEngine.Debug.LogError("Forced cancellation - the recording process ends abnormally and the output file may not be played."); Process.GetProcessById(_pid).Kill(); _isRecording = false; } catch (Exception e) { UnityEngine.Debug.LogError("FFRecorder::OnDestroy - " + e.Message); } } if (_OnStopRecording != null) { _OnStopRecording(); } } private bool ParseSettings() { _ffpath = Application.streamingAssetsPath + @"/ffmpeg/ffmpeg.exe"; string name = Application.productName + "_" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"); curVideoName = name + ".mp4"; // resolution string s; if (resolution == Resolution._1280x720) { int w = 1280; int h = 720; if (Screen.width < w) { w = Screen.width; UnityEngine.Debug.LogWarning(string.Format("The recording horizontal resolution is greater than the screen horizontal resolution and has been automatically reduced to {0}.", w)); } if (Screen.height < h) { h = Screen.height; UnityEngine.Debug.LogWarning(string.Format("The recording vertical resolution is greater than the screen vertical resolution and has been automatically reduced to {0}.", h)); } s = w.ToString() + "x" + h.ToString(); } else if (resolution == Resolution._1920x1080) { int w = 1920; int h = 1080; if (Screen.width < w) { w = Screen.width; UnityEngine.Debug.LogWarning(string.Format("The recording horizontal resolution is greater than the screen horizontal resolution and has been automatically reduced to {0}.", w)); } if (Screen.height < h) { h = Screen.height; UnityEngine.Debug.LogWarning(string.Format("The recording vertical resolution is greater than the screen vertical resolution and has been automatically reduced to {0}.", h)); } s = w.ToString() + "x" + h.ToString(); } else /*(resolution == Resolution._Auto)*/ { s = Screen.width.ToString() + "x" + Screen.height.ToString(); } // frame rate string r = framerate.ToString().Remove(0, 1); //bitrate string b = bitrate.ToString().Remove(0, 1); //output position string output; if (outputPath == OutputPath.Desktop) output = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "/" + Application.productName + "_Records"; else if (outputPath == OutputPath.DataPath) output = Application.dataPath + "/" + Application.productName + "_Records"; else if (outputPath == OutputPath.StreamingAsset) output = Application.streamingAssetsPath + "/" + Application.productName + "_Records"; else if (outputPath == OutputPath.PersistentDataPath) output = Application.persistentDataPath + "/" + Application.productName + "_Records"; else /*(outputPath == OutputPath.Custom)*/ output = customOutputPath; //Command line parameters if (recordType == RecordType.GDIGRAB) { _ffargs = string.Format(FFARGS_GDIGRAB, Application.productName, b, r, s, output, name); } else /*(recordType == RecordType.DSHOW)*/ { _ffargs = string.Format(FFARGS_DSHOW, b, r, s, output, name); } //Create output folder if (!System.IO.Directory.Exists(output)) { try { System.IO.Directory.CreateDirectory(output); } catch (Exception e) { UnityEngine.Debug.LogError("FFRecorder::ParseSettings - " + e.Message); return false; } } return true; } // It is not necessary to use coroutines private IEnumerator IERecording() { yield return null; Process ffp = new Process(); ffp.StartInfo.FileName = _ffpath; // Process executable file location ffp.StartInfo.Arguments = _ffargs; // Command line parameters passed to the executable file ffp.StartInfo.CreateNoWindow = !_debug; // Whether to display the console window ffp.StartInfo.UseShellExecute = _debug; // Whether to use the operating system Shell program to start the process ffp.Start(); // Start the process _pid = ffp.Id; _isRecording = true; } private IEnumerator IEExitCmd(Action _OnStopRecording) { // Attach the current process to the console of the pid process AttachConsole(_pid); //Set the console event handler to Zero, that is, the current process does not respond to console events // Avoid ending the current process when sending the [Ctrl C] command to the console SetConsoleCtrlHandler(IntPtr.Zero, true); //Send [Ctrl C] end command to the console // ffmpeg will receive this command to stop recording GenerateConsoleCtrlEvent(0, 0); // ffmpeg cannot be stopped immediately, wait for a while, otherwise the video cannot be played yield return new WaitForSeconds(3.0f); // Uninstall the console event handler, otherwise subsequent ffmpeg calls cannot stop normally. SetConsoleCtrlHandler(IntPtr.Zero, false); // Strip the attached console FreeConsole(); _isRecording = false; if (_OnStopRecording != null) { _OnStopRecording(); } UploadRecordVideo(); } private IEnumerator FocusIEExitCmd(Action _OnStopRecording) { // Attach the current process to the console of the pid process AttachConsole(_pid); //Set the console event handler to Zero, that is, the current process does not respond to console events // Avoid ending the current process when sending the [Ctrl C] command to the console SetConsoleCtrlHandler(IntPtr.Zero, true); //Send [Ctrl C] end command to the console // ffmpeg will receive this command to stop recording GenerateConsoleCtrlEvent(0, 0); // ffmpeg cannot be stopped immediately, wait for a while, otherwise the video cannot be played yield return new WaitForSeconds(3.0f); // Uninstall the console event handler, otherwise subsequent ffmpeg calls cannot stop normally. SetConsoleCtrlHandler(IntPtr.Zero, false); // Strip the attached console FreeConsole(); _isRecording = false; if (_OnStopRecording != null) { _OnStopRecording(); } string filePath = Application.persistentDataPath + "/" + Application.productName + "_Records" + "/" + curVideoName; StartCoroutine(DeleteVideoFile(filePath)); } /// <summary> /// Upload screen recording video /// </summary> /// <returns></returns> private void UploadRecordVideo() { if (currentVideoRoutine != null) return; currentVideoRoutine = DoUploadRecordVideo(); StartCoroutine(currentVideoRoutine); } private IEnumerator DoUploadRecordVideo() { yield return new WaitForSeconds(1.0f); string filePath = Application.persistentDataPath + "/" + Application.productName + "_Records" + "/" + curVideoName; if (!IsOccupied(filePath, "Upload video")) { print("Press video file"); currentVideoRoutine = null; } else { currentVideoRoutine = null; UploadRecordVideo(); } } // When the program ends, the background recording process must be killed, but this will cause the output file to be unable to be played. public void OnDestroy() { if (_isRecording) { try { UnityEngine.Debug.LogError("FFRecorder::OnDestroy - The recording process ended abnormally and the output file may not be played."); Process.GetProcessById(_pid).Kill(); _isRecording = false; } catch (Exception e) { UnityEngine.Debug.LogError("FFRecorder::OnDestroy - " + e.Message); } } } /// <summary> /// Delete video files /// </summary> /// <param name="filePath"></param> /// <returns></returns> private IEnumerator DeleteVideoFile(string filePath) { yield return new WaitForSeconds(1.0f); if (!IsOccupied(filePath, "Delete video")) { File.Delete(filePath); UnityEngine.Debug.Log("Cleaning up abnormal video completed!"); } else { UnityEngine.Debug.Log("Failed to delete abnormal video"); //StartCoroutine(DeleteVideoFile(filePath)); } } #region tools /// <summary> /// Determine whether the file is occupied /// </summary> /// <param name="filePath"></param> /// <returns></returns> public bool IsOccupied(string filePath, string fileFrom) { FileStream stream = null; try { stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); return false; } catch { print(fileFrom + "File is occupied"); return true; } finally { if (stream != null) { stream.Close(); } } } #endregion }