带有嵌入式窗口的UI滞后

时间:2018-09-11 11:41:33

标签: c# wpf unity3d

与大多数UI滞后问题一样,根本原因通常是调度程序线程上发生了太多事情。但是,据我所知(source from a seperate question),进程在通往主窗口的单独线程上运行,因此这不应成为问题的起因。

如果我在这一点上错了,请纠正我。


我最初是从SO question

开始研究如何将Unity应用程序嵌入到WPF应用程序中的。

这使我能够开发出一个具有嵌入式Unity窗口的应用程序。

问题是UI滞后时间很长。当我尝试在窗口上移动滑块时,要花上几秒钟才能赶上鼠标。

此UI滞后的原因是什么?如何解决?

我的MainWindow如下所示。

enter image description here

隐藏代码

using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.ComponentModel;
using System.Threading;

namespace CerbyEmbeddedUnity
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);

        internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
        [DllImport("user32.dll")]
        internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        private Process unityProcess;
        private IntPtr unityHWND = IntPtr.Zero;

        private const int WM_ACTIVATE = 0x0006;
        private readonly IntPtr WA_ACTIVE = new IntPtr(1);
        private readonly IntPtr WA_INACTIVE = new IntPtr(0);

        private bool isFirstTimeActivating = true;
        private bool isFirstTimeDeactivating = true;
        private bool processActivatedSuccessfully = false;
        private const string PATH = @"..\..\..\..\Unity Side\Embedded Unity Demo\Build\Embedded Unity Demo.exe";

        public MainWindow()
        {
            InitializeComponent();
            SetUpUnityWindow();
        }

        private void SetUpUnityWindow()
        {
            bool exeExists = File.Exists(PATH);
            Console.WriteLine($"{Path.GetFullPath(PATH)} {(exeExists ? "does exist" : "doesn't exist")}"); //As PATH is relative, this helps make sure it is correct

            if(!exeExists)
            {
                //Unity .exe missing
                return;
            }

            IntPtr handle = UnityPanel.Handle;

            unityProcess = new Process();
            unityProcess.StartInfo.FileName = PATH;
            unityProcess.StartInfo.Arguments = "-parentHWND " + handle.ToInt32() + " " + Environment.CommandLine;
            unityProcess.StartInfo.UseShellExecute = true;
            unityProcess.StartInfo.CreateNoWindow = true;

            try
            {
                unityProcess.Start();
            }
            catch (InvalidOperationException)
            {
                //Something went wrong setting up the unity process
                return;
            }

            unityProcess.WaitForInputIdle();

            processActivatedSuccessfully = true;

            EnumChildWindows(handle, WindowEnum, IntPtr.Zero);

            unityHWNDLabel.Content = "Unity HWND: 0x" + unityHWND.ToString("X8");
        }

        private void ActivateUnityWindow()
        {
            SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
        }

        private void DeactivateUnityWindow()
        {
            SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
        }

        private int WindowEnum(IntPtr hwnd, IntPtr lparam)
        {
            unityHWND = hwnd;
            ActivateUnityWindow();
            return 0;
        }

        private void OnUnityPanelResize(object sender, EventArgs e)
        {
            MoveWindow(unityHWND, 0, 0, UnityPanel.Width, UnityPanel.Height, true);
            ActivateUnityWindow();
        }

        public void OnWindowClosing(object sender, CancelEventArgs e)
        {
            if(!processActivatedSuccessfully)
            {
                //Unity process didn't start. Nothing to close
                return;
            }

            try
            {
                unityProcess.CloseMainWindow();
            }
            catch (InvalidOperationException)
            {
                //Unity process already dead
                return;
            }

            Thread.Sleep(1000);
            while (!unityProcess.HasExited)
            {
                unityProcess.Kill();
            }
        }

        private void Activate(object sender, EventArgs e)
        {
            if(isFirstTimeActivating)
            {
                isFirstTimeActivating = false;
                ActivateUnityWindow();
            }
        }

        private void Deactivate(object sender, EventArgs e)
        {
            if(isFirstTimeDeactivating)
            {
                isFirstTimeDeactivating = false;
                DeactivateUnityWindow();
            }
        }
    }
}

XAML

<Window x:Class="CerbyEmbeddedUnity.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d"
        Activated="Activate"
        Deactivated="Deactivate"
        Closing="OnWindowClosing"
        WindowState="Maximized"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <WindowsFormsHost Grid.Column="1">
            <wf:Panel x:Name="UnityPanel" Resize="OnUnityPanelResize"/>
        </WindowsFormsHost>
        <StackPanel>
            <Label Name="unityHWNDLabel" HorizontalAlignment="Center"/>

            <!-- #region X -->
            <StackPanel Margin="4">
                <Label Content="X" FontSize="25" FontWeight="Bold" HorizontalAlignment="Center"/>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="auto"/>
                    </Grid.ColumnDefinitions>

                    <Label Grid.Row="1"
                           Content="X Rotation Speed"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           VerticalContentAlignment="Center"/>
                    <Slider Grid.Row="1" Grid.Column="1"
                            Minimum="0" Maximum="100"
                            VerticalAlignment="Center"/>
                    <Label Grid.Row="1" Grid.Column="2"
                           HorizontalAlignment="Center"
                           Content="000" ContentStringFormat="D3"
                           VerticalAlignment="Center"
                           VerticalContentAlignment="Center"/>
                </Grid>
            </StackPanel>
            <!-- #endregion -->
        </StackPanel>
    </Grid>
</Window>

我在这里使用的Unity项目非常简单。它由一个摄像头和一个位于摄像头前面的立方体组成,上面带有下面的脚本。

简单的Unity旋转脚本

using UnityEngine;

public class CubeRotation : MonoBehaviour
{
    [SerializeField]
    [Range(0, 100)]
    private float rotationSpeedX = 1;

    [SerializeField]
    [Range(0, 360)]
    private float xAngle = 0;

    [SerializeField]
    [Range(0, 360)]
    private float yAngle = 0;

    [SerializeField]
    [Range(0, 360)]
    private float zAngle = 0;

    [SerializeField]
    private bool canXRotate = true;

    private void LateUpdate()
    {
        if(canXRotate)
        {
            xAngle += rotationSpeedX;

            if (xAngle > 360)
            {
                xAngle -= 360;
            }
        }

        transform.localEulerAngles = new Vector3(xAngle, yAngle, zAngle);
    }
}

0 个答案:

没有答案