You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 部署与测试

  1. 将SingleInstanceManager、FilePipeListener、UnityMainThreadDispatcher挂载到初始场景的同一个持久化空物体上。
  2. 打包游戏后,启动游戏实例,再双击.myRhythmGame文件,验证新实例自动退出,已有实例聚焦并加载文件。

内容的提问来源于stack exchange,提问作者user25501056

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 18:43:12