Unity开发类Osu!节奏游戏:已启动游戏时的文件处理问题
解决方案:Unity游戏已运行时处理自定义文件关联
1. 实现单例检测与跨实例参数传递
要解决游戏已运行时的文件处理问题,核心是确保游戏仅运行一个实例,同时让新启动的实例把文件路径传递给已有实例后自动退出,这里用互斥体+命名管道的组合实现,稳定性更高。
步骤1:单例实例检测与参数转发
创建单例管理脚本,启动时检查是否已有游戏实例,若有则传递文件路径并退出:
using System; using System.Threading; using UnityEngine; public class SingleInstanceManager : MonoBehaviour { private static Mutex _instanceMutex; private const string MutexUniqueName = "MyRhythmGame_SingleInstanceLock"; void Awake() { bool isNewInstance; // 创建互斥体,检测是否已有实例运行 _instanceMutex = new Mutex(true, MutexUniqueName, out isNewInstance); if (!isNewInstance) { // 传递命令行中的文件路径给已有实例 ForwardFilePathToRunningInstance(); Application.Quit(); return; } // 保持脚本所在对象不被销毁 DontDestroyOnLoad(gameObject); } private void ForwardFilePathToRunningInstance() { string[] cmdArgs = Environment.GetCommandLineArgs(); if (cmdArgs.Length <= 1) return; string targetFilePath = cmdArgs[1]; // 通过命名管道发送文件路径 using (var pipeClient = new System.IO.Pipes.NamedPipeClientStream(".", "MyRhythmGame_FilePipe", System.IO.Pipes.PipeDirection.Out)) { try { pipeClient.Connect(1000); using (var writer = new System.IO.StreamWriter(pipeClient)) { writer.WriteLine(targetFilePath); writer.Flush(); } } catch { /* 忽略连接失败,比如已有实例刚退出 */ } } } void OnApplicationQuit() { _instanceMutex?.ReleaseMutex(); _instanceMutex?.Dispose(); } }
步骤2:已有实例监听管道并处理文件
创建管道监听脚本,在后台接收新实例传递的文件路径,然后在主线程加载文件并聚焦窗口:
using System; using System.IO.Pipes; using System.Threading; using UnityEngine; public class FilePipeListener : MonoBehaviour { private Thread _listenThread; void Start() { // 启动后台线程监听管道 _listenThread = new Thread(ListenForFilePaths); _listenThread.IsBackground = true; _listenThread.Start(); } private void ListenForFilePaths() { while (true) { using (var pipeServer = new NamedPipeServerStream("MyRhythmGame_FilePipe", PipeDirection.In)) { pipeServer.WaitForConnection(); using (var reader = new System.IO.StreamReader(pipeServer)) { string filePath = reader.ReadLine(); if (!string.IsNullOrEmpty(filePath)) { // 切换到主线程执行Unity API操作 UnityMainThreadDispatcher.Instance.Enqueue(() => { FocusGameWindow(); LoadAndInitializeGame(filePath); }); } } } } } private void FocusGameWindow() { // 获取Unity游戏窗口句柄并聚焦 IntPtr windowHandle = GetUnityWindowHandle(); SetForegroundWindow(windowHandle); // 若窗口最小化则恢复 ShowWindow(windowHandle, 9); // SW_RESTORE常量值为9 } // 获取Unity游戏窗口的句柄 private IntPtr GetUnityWindowHandle() { return System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; } // 导入Windows API函数 [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); }
补充:主线程调度工具
因为管道监听在子线程,不能直接调用Unity API,需要一个主线程调度器:
using System.Collections.Generic; using UnityEngine; public class UnityMainThreadDispatcher : MonoBehaviour { public static UnityMainThreadDispatcher Instance { get; private set; } private readonly Queue<Action> _pendingActions = new Queue<Action>(); void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } void Update() { lock (_pendingActions) { while (_pendingActions.Count > 0) { _pendingActions.Dequeue().Invoke(); } } } public void Enqueue(Action action) { lock (_pendingActions) { _pendingActions.Enqueue(action); } } }
2. 注册表配置确认
确保自定义文件关联的注册表命令行参数正确传递文件路径,command值格式应为:
"C:\YourGamePath\Game.exe" "%1"
这样每次打开.myRhythmGame文件时,系统都会把文件路径作为参数传给游戏进程。
3. 部署与测试
- 将
SingleInstanceManager、FilePipeListener、UnityMainThreadDispatcher挂载到初始场景的同一个持久化空物体上。 - 打包游戏后,启动游戏实例,再双击
.myRhythmGame文件,验证新实例自动退出,已有实例聚焦并加载文件。
内容的提问来源于stack exchange,提问作者user25501056
相关产品推荐
相关产品推荐

