Unreal Engine macOS切换窗口模式时崩溃无响应问题求助
UE项目macOS下指定显示器切换全屏/窗口崩溃无响应问题
开发Unreal Engine项目时,在macOS系统中切换选定显示器的全屏/窗口模式,应用会出现崩溃或无响应情况。Windows系统下运行正常,但macOS上操作后游戏窗口冻结,崩溃后仅能移动鼠标无法点击,只能强制退出。当前实现的ApplyFullscreenToMonitor()函数负责将全屏模式应用到指定显示器,逻辑为获取显示指标、根据显示器分辨率和位置调整窗口,代码如下:
void UEtrResolutionSetting::ApplyFullscreenToMonitor() { // Get game user settings UGameUserSettings* GameUserSettings = GEngine->GetGameUserSettings(); if (GameUserSettings) { FDisplayMetrics displayMetrics; FSlateApplication::Get().GetDisplayMetrics(displayMetrics); const int32 num = displayMetrics.MonitorInfo.Num(); int32 chosenIndex = -1; int32 primaryIndex = 0; for (int32 i = 0; i < num; i++) { FMonitorInfo& info = displayMetrics.MonitorInfo[i]; if (info.Name == SelectedMonitor.Name) { chosenIndex = i; break; } if (info.bIsPrimary) { primaryIndex = i; } } int32 finalIndex = (chosenIndex < 0) ? primaryIndex : chosenIndex; if (chosenIndex < 0 && !SelectedMonitor.Name.IsEmpty()) { // NOTE: Log something here } const FMonitorInfo& info = displayMetrics.MonitorInfo[finalIndex]; const FIntPoint resolution = GameUserSettings->GetScreenResolution(); if (resolution.X > info.NativeWidth || resolution.Y > info.NativeHeight) { GameUserSettings->SetScreenResolution({ info.NativeWidth, info.NativeHeight }); } GameUserSettings->SetWindowPosition(info.WorkArea.Left, info.WorkArea.Top); // Move the window to the selected monitor and resize if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWindow()) { TSharedPtr<SWindow> window = GEngine->GameViewport->GetWindow(); EWindowMode::Type const WindowMode = GEngine->GetGameUserSettings()->GetFullscreenMode(); window->MoveWindowTo(GameUserSettings->GetWindowPosition()); window->Resize(GameUserSettings->GetScreenResolution()); window->SetWindowMode(WindowMode); GameUserSettings->SetFullscreenMode(WindowMode); GameUserSettings->ApplyResolutionSettings(false); } else { mDelegateHandleViewportCreated = UGameViewportClient::OnViewportCreated().AddUObject( this, &UEtrResolutionSetting::ApplyFullscreenToMonitor); } if (mDelegateHandleViewportCreated.IsValid()) { UGameViewportClient::OnViewportCreated().Remove(mDelegateHandleViewportCreated); mDelegateHandleViewportCreated.Reset(); } // Log successful fullscreen change on the specific monitor UE_LOG(LogTemp, Log, TEXT("Game switched to fullscreen on monitor: %s with resolution %dx%d"), *SelectedMonitor.Name, resolution.X, resolution.Y); } else { UE_LOG(LogTemp, Error, TEXT("Failed to get GameUserSettings")); } }
问题根源
- 委托逻辑错误:代码中在
else分支添加OnViewportCreated委托后,立刻移除委托,导致委托永远不会触发,窗口未初始化时无法正确执行逻辑。 - 窗口操作顺序不符合macOS机制:直接调用
SWindow的移动、 resize、切换窗口模式方法,macOS下Slate窗口的全屏切换需要特定顺序,直接操作可能引发UI线程阻塞或死锁。 - 重复状态变更:同时调用
window->SetWindowMode和GameUserSettings->SetFullscreenMode,触发重复的窗口状态变更,引发冲突。
修复方案
- 修正委托逻辑:仅在窗口未初始化时添加委托,且在委托触发后再移除,确保窗口创建完成后执行全屏切换。
- 调整窗口操作流程:依赖
GameUserSettings的ApplyResolutionSettings统一处理窗口状态,避免直接调用Slate窗口方法;macOS下需先切换到窗口模式,调整位置后再切换到全屏。 - 确保UI操作在主线程执行:使用延迟队列将窗口操作放到主线程执行,避免跨线程操作冲突。
修改后的代码
void UEtrResolutionSetting::ApplyFullscreenToMonitor() { UGameUserSettings* GameUserSettings = GEngine->GetGameUserSettings(); if (!GameUserSettings) { UE_LOG(LogTemp, Error, TEXT("Failed to get GameUserSettings")); return; } FDisplayMetrics displayMetrics; FSlateApplication::Get().GetDisplayMetrics(displayMetrics); const int32 MonitorCount = displayMetrics.MonitorInfo.Num(); int32 ChosenIndex = -1; int32 PrimaryIndex = 0; for (int32 i = 0; i < MonitorCount; i++) { FMonitorInfo& Info = displayMetrics.MonitorInfo[i]; if (Info.Name == SelectedMonitor.Name) { ChosenIndex = i; break; } if (Info.bIsPrimary) { PrimaryIndex = i; } } const int32 FinalIndex = (ChosenIndex >= 0) ? ChosenIndex : PrimaryIndex; if (ChosenIndex < 0 && !SelectedMonitor.Name.IsEmpty()) { UE_LOG(LogTemp, Warning, TEXT("Selected monitor %s not found, falling back to primary"), *SelectedMonitor.Name); } const FMonitorInfo& TargetMonitor = displayMetrics.MonitorInfo[FinalIndex]; const FIntPoint CurrentResolution = GameUserSettings->GetScreenResolution(); const FIntPoint TargetResolution = FIntPoint(TargetMonitor.NativeWidth, TargetMonitor.NativeHeight); // 限制分辨率不超过显示器原生尺寸 if (CurrentResolution.X > TargetResolution.X || CurrentResolution.Y > TargetResolution.Y) { GameUserSettings->SetScreenResolution(TargetResolution); } GameUserSettings->SetWindowPosition(TargetMonitor.WorkArea.Left, TargetMonitor.WorkArea.Top); if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWindow()) { TSharedPtr<SWindow> GameWindow = GEngine->GameViewport->GetWindow(); EWindowMode::Type TargetWindowMode = GameUserSettings->GetFullscreenMode(); // macOS下需先切换到窗口模式再调整位置,避免全屏状态下移动窗口引发冲突 if (TargetWindowMode == EWindowMode::Fullscreen && FPlatformProperties::IsMac()) { // 延迟到主线程执行,避免UI线程阻塞 FSlateApplication::Get().GetMainThreadWidget()->QueueDeferredAction([this, GameWindow, GameUserSettings, TargetWindowMode]() { GameWindow->SetWindowMode(EWindowMode::Windowed); GameWindow->MoveWindowTo(GameUserSettings->GetWindowPosition()); GameUserSettings->SetFullscreenMode(TargetWindowMode); GameUserSettings->ApplyResolutionSettings(false); }); } else { GameUserSettings->SetFullscreenMode(TargetWindowMode); GameUserSettings->ApplyResolutionSettings(false); } } else { // 仅当委托未添加时才注册,避免重复注册 if (!mDelegateHandleViewportCreated.IsValid()) { mDelegateHandleViewportCreated = UGameViewportClient::OnViewportCreated().AddUObject(this, &UEtrResolutionSetting::ApplyFullscreenToMonitor); } return; } // 移除已完成任务的委托 if (mDelegateHandleViewportCreated.IsValid()) { UGameViewportClient::OnViewportCreated().Remove(mDelegateHandleViewportCreated); mDelegateHandleViewportCreated.Reset(); } UE_LOG(LogTemp, Log, TEXT("Game switched to %s on monitor: %s with resolution %dx%d"), *UEnum::GetValueAsString(GameUserSettings->GetFullscreenMode()), *SelectedMonitor.Name, GameUserSettings->GetScreenResolution().X, GameUserSettings->GetScreenResolution().Y); }
额外注意事项
- macOS的全屏模式分为原生全屏(
EWindowMode::Fullscreen)和窗口全屏(EWindowMode::WindowedFullscreen),原生全屏会占用整个显示器,切换时需要系统级权限,可能引发兼容性问题,建议优先使用窗口全屏模式。 - 测试时需关闭macOS的“自动隐藏菜单栏”和“自动隐藏程序坞”选项,避免窗口状态变更时系统UI干扰。
内容的提问来源于stack exchange,提问作者Rasik
相关产品推荐
相关产品推荐

