Нужна помощь в динамическом изменении параметров окна программы на Unity для Windows

Вот что должно быть реализовано: Есть 2 сцены. Первая по дефолту настроена как полноэкранная безрамочная. По нажатию кнопки происходит переход на вторую сцену, при этом параметры окна программы должны измениться:

  • Окно должно перестать быть полноэкранным, но остаться безрамочным
  • Фон окна прозрачный. Должна быть видна только png картинка, расположенная на сцене
  • Размер окна должен измениться на 128х128 пикселей
  • Расположение окна случайное в пределах экрана (но чтобы не заходило за его границы)
  • Окно должно быть всегда поверх других приложений

При нажатии на эту картинку должен произойти возврат на первую сцену, при этом все параметры выше должны вернуться в норму, а именно:

  • Окно снова должно стать полноэкранным безрамочным
  • При выходе из полноэкранного режима размер окна должен совпадать с расширением экрана и находиться в центре экрана
  • Фон окна должен перестать быть прозрачным
  • Окно больше не должно быть постоянно поверх других приложений

Вот одна из многочисленных более-менее работающих попыток реализации перехода на вторую сцену, которую я писал не без помощи ии:

 using UnityEngine;
 using System.Runtime.InteropServices;
 using System;
 using UnityEngine.SceneManagement;
 using System.Diagnostics;
 public class GameController : MonoBehaviour
 {
     [DllImport("user32.dll")]
     private static extern IntPtr GetActiveWindow();
     [DllImport("user32.dll")]
     private static extern bool SetWindowLong(IntPtr hwnd, int nIndex, uint dwNewLong);
     [DllImport("user32.dll")]
     private static extern uint GetWindowLong(IntPtr hwnd, int nIndex);
     [DllImport("user32.dll")]
     private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
     [DllImport("user32.dll")]
     private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

     private const uint SWP_NOZORDER = 0x0004;
     private const uint SWP_NOACTIVATE = 0x0010;
     private const int GWL_EXSTYLE = -20;
     private const uint WS_EX_LAYERED = 0x00080000;
     private const uint WS_EX_TOOLWINDOW = 0x00000080;
     private const uint WS_EX_TOPMOST = 0x00000008;
     private const uint LWA_ALPHA = 0x00000002;

     private void Start()
     {
         SceneManager.activeSceneChanged += OnSceneChanged;
     }
     private void OnSceneChanged(Scene previous, Scene current)
     {
         if(current.name == "SecondScene")
         {
             SetWindowProperties();
         }

     }
     public void SetWindowProperties()
     {
         Screen.SetResolution(128, 128, false);
         SetWinsdowPosition(0, 0);

         IntPtr hwnd = GetActiveWindow();
         uint exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

         exStyle |= WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
         SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
         SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA); 
     }
     private void SetWinsdowPosition(int x, int y) 
     {
         IntPtr hwnd = GetActiveWindow();
         SetWindowPos(hwnd, IntPtr.Zero, x, y, 128, 128, SWP_NOZORDER | SWP_NOACTIVATE);
     }
     }

При этом окно всё равно получается корявым и не изменяет большую часть необходимых параметров


Ответы (1 шт):

Автор решения: FrozenM

Чисто ради прикола загнал твой вопрос в DeepSeek. Если не поможет, мой ответ удалю. Просто интересно, может ли ИИ разбирать твои вопросы ;)) Вот ответ дипсика: Ваш код близок к решению, но требует доработок для полного соответствия требованиям. Вот исправленная и дополненная реализация:

using UnityEngine;
using System.Runtime.InteropServices;
using UnityEngine.SceneManagement;
using Random = UnityEngine.Random;

public class GameController : MonoBehaviour
{
    // Импорт функций WinAPI
    [DllImport("user32.dll")]
    private static extern IntPtr GetActiveWindow();
    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hwnd, int nIndex, uint dwNewLong);
    [DllImport("user32.dll")]
    private static extern uint GetWindowLong(IntPtr hwnd, int nIndex);
    [DllImport("user32.dll")]
    private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    [DllImport("user32.dll")]
    private static extern int GetSystemMetrics(int nIndex);

    // Константы WinAPI
    private const int GWL_EXSTYLE = -20;
    private const uint WS_EX_LAYERED = 0x00080000;
    private const uint WS_EX_TOPMOST = 0x00000008;
    private const uint WS_EX_TRANSPARENT = 0x00000020;
    private const uint LWA_ALPHA = 0x00000002;
    private const int HWND_TOPMOST = -1;
    private const int HWND_NOTOPMOST = -2;
    private const uint SWP_SHOWWINDOW = 0x0040;
    private const uint SWP_NOSIZE = 0x0001;
    private const uint SWP_NOMOVE = 0x0002;
    private const int SM_CXSCREEN = 0;
    private const int SM_CYSCREEN = 1;

    private static IntPtr hwnd;
    private static uint originalStyle;
    private static Vector2Int originalResolution;

    private void Start()
    {
        hwnd = GetActiveWindow();
        originalStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
        originalResolution = new Vector2Int(Screen.width, Screen.height);
        
        SceneManager.activeSceneChanged += OnSceneChanged;
    }

    private void OnDestroy()
    {
        SceneManager.activeSceneChanged -= OnSceneChanged;
    }

    private void OnSceneChanged(Scene prev, Scene next)
    {
        if (next.name == "SecondScene") SetSecondSceneMode();
        else if (next.name == "FirstScene") RestoreFirstSceneMode();
    }

    public void SetSecondSceneMode()
    {
        // Настройки камеры для прозрачности (должны быть в префабе второй сцены)
        Camera.main.clearFlags = CameraClearFlags.Depth;
        Camera.main.backgroundColor = new Color(0,0,0,0);

        // Параметры окна
        int width = 128;
        int height = 128;
        Screen.SetResolution(width, height, FullScreenMode.Windowed);

        // Случайная позиция в пределах экрана
        int screenWidth = GetSystemMetrics(SM_CXSCREEN);
        int screenHeight = GetSystemMetrics(SM_CYSCREEN);
        int x = Random.Range(0, screenWidth - width);
        int y = Random.Range(0, screenHeight - height);

        // Стиль окна
        uint newStyle = originalStyle | WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TRANSPARENT;
        SetWindowLong(hwnd, GWL_EXSTYLE, newStyle);
        
        // Применение параметров
        SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
        SetWindowPos(hwnd, HWND_TOPMOST, x, y, width, height, SWP_SHOWWINDOW);
    }

    public void RestoreFirstSceneMode()
    {
        // Восстановление стиля окна
        SetWindowLong(hwnd, GWL_EXSTYLE, originalStyle);
        
        // Центрирование и полноэкранный режим
        int screenWidth = GetSystemMetrics(SM_CXSCREEN);
        int screenHeight = GetSystemMetrics(SM_CYSCREEN);
        int x = (screenWidth - originalResolution.x) / 2;
        int y = (screenHeight - originalResolution.y) / 2;
        
        Screen.SetResolution(
            originalResolution.x,
            originalResolution.y,
            FullScreenMode.FullScreenWindow
        );

        // Снятие режима "Поверх всех окон"
        SetWindowPos(hwnd, HWND_NOTOPMOST, x, y, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
    }

    // Для вызова из UI
    public void LoadSecondScene() => SceneManager.LoadScene("SecondScene");
    public void LoadFirstScene() => SceneManager.LoadScene("FirstScene");
}

Ключевые исправления:

  1. Прозрачность окна:
  • Добавлен флаг WS_EX_TRANSPARENT

  • Камера на второй сцене должна иметь настройки:

    • Clear Flags: Depth Only
    • Background: RGBA(0,0,0,0)
  • В Player Settings → Resolution and Presentation:
    • Enable "Resizable Window"
    • Enable "Use DXGI Flip Model"
  1. Случайная позиция:
  • Используется GetSystemMetrics() для получения размеров экрана
  • Корректный расчет позиции с учетом границ экрана
  1. Режим "Поверх всех":
  • Исправлены флаги HWND_TOPMOST/HWND_NOTOPMOST
  • Правильное восстановление обычного режима
  1. Восстановление первой сцены:
  • Сохранение оригинальных параметров окна
  • Автоматический расчет центрированной позиции
  • Корректный выход из полноэкранного режима
  1. Оптимизация:
  • Кэширование handle окна
  • Сохранение оригинальных параметров
  • Упрощенное управление сценами

Важные требования к сценам:

  1. Первая сцена:
  • Кнопка должна вызывать GameController.LoadSecondScene()
  • Камера: Clear Flags = Skybox/Solid Color
  1. Вторая сцена:
  • PNG-объект должен иметь коллайдер
  • На PNG-объекте: компонент Button → вызывает LoadFirstScene()
  • Камера: Clear Flags = Depth Only, Background = (0,0,0,0)

Дополнительные настройки проекта:

  1. В Build Settings добавьте обе сцены
  2. В Player Settings:
  • Fullscreen Mode: Fullscreen Window
  • Disable "Use Player Log"
  • Graphics API: DirectX11 или DirectX12

Это решение гарантирует:

  • Полноэкранный безрамочный режим в первой сцене
  • Маленькое прозрачное окно во второй сцене
  • Случайное позиционирование без выхода за границы экрана
  • Корректное восстановление параметров при возврате
  • Режим "поверх всех окон" только для второй сцены
→ Ссылка