【问题标题】:WPF Games: How can I improve animation of my game map?WPF Games:如何改进游戏地图的动画?
【发布时间】:2016-10-09 03:25:16
【问题描述】:

我正在用 WPF 制作游戏。我选择 WPF 是因为我想在我的游戏中使用布局容器和用户控件。游戏有两个部分:1.) 一个角色四处走动的地图,以及 2.) 一个 UserControl,它由许多容器、控件和其他充当另一个屏幕的 userControls 组成。我需要能够从地图转换到 UserControl 屏幕。

UserControl 部分效果很好,但我很难为地图制作动画,这是一个大约 100x100 像素的 8x5 网格图块。随着地图的移动,玩家在屏幕中居中:

ScreenShot of test map

我尝试了几种方法来渲染地图。我使用了一个计时器,CompositionTarget.Rendering 事件,现在我已经重写了 OnRender 方法以使用 DrawingVisuals 绘制到图像。到目前为止,我能做的最好的事情是一次移动地图 1 个像素以平滑动画,但运动太慢了。在所有情况下,帧速率都会定期减半。 RenderTime 方法给出的更新时间模式类似于:16、17、16、17、16、17、33、16、17、16、0、16、17,...(毫秒)。如果我知道什么时候会提前丢帧,我可以通过改变我移动地图的量来补偿。

有没有办法做到这一点,或者有没有其他方法可以完成这项工作?我愿意使用任何允许 1.) 和 2.) 发生的方法,但 userControl 必须是 WPF 控件(因为我在这方面花了很多时间。)谢谢!

更新 :以下 WPF 项目是我在平滑渲染我提到的 8x5 地图方面的最新尝试。

MainWindow.xaml 文件:

<Window x:Class="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:local="clr-namespace:RenderingBitmapTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" WindowState="Minimized">
    <Grid>
        <Image Name="Background"/>
        <Button Name="MoveDownButton" Height="35" Width="100" Content="Press Me!" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
        <Ellipse HorizontalAlignment="Center" VerticalAlignment="Center" Stroke="Black" StrokeThickness="3" Fill="Blue" Height="50" Width="50"/>
    </Grid>
</Window>

这是 MainWindow.xaml.vb 文件:

Imports System.Security.Permissions
Imports System.Windows.Threading

Class MainWindow
    Private buffer As RenderTargetBitmap
    Private drawingVisual As New DrawingVisual

    ' FPS counter '
    Dim tSec As Integer = TimeOfDay.Second
    Dim tTicks As Integer = 0
    Dim MaxTicks As Integer = 0

    ' Motion Testing '
    Dim moveX As Double = 0
    Dim moveY As Double = 0

    Dim IsRunning As Boolean = True

    Dim grass As New BitmapImage(New Uri("pack://application:,,,/Resources/GrassTile2.png")) 'Image is 100 x 100 '
    Dim tree As New BitmapImage(New Uri("pack://application:,,,/Resources/Tree2.png"))

    Protected Overrides Sub OnRender(ByVal drawingContext As DrawingContext)
        MyBase.OnRender(drawingContext)
        buffer = New RenderTargetBitmap(CInt(Me.ActualWidth), CInt(Me.ActualHeight), 96, 96, PixelFormats.Pbgra32)
        Background.Source = buffer
    End Sub

    Private Sub Drawing()
        If buffer Is Nothing Then
            Return
        End If

        buffer.Clear()

        Using dc As DrawingContext = drawingVisual.RenderOpen()

            For X = 0 To 7
                For Y = 0 To 4
                    dc.DrawImage(grass, New Rect(moveX + Me.Width / 7 * X, moveY + Me.Height / 5 * Y, Me.Width / 6.9, Me.Height / 4.9))
                    dc.DrawText(New FormattedText(MaxTicks, Globalization.CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, New Typeface("Verdana"), 32, Brushes.Black), New Point(0, 0))
                Next
            Next
            'Trees added. '
            dc.DrawImage(tree, New Rect(moveX + 700, moveY + 400, Me.Width / 8, Me.Height / 3))
            dc.DrawImage(tree, New Rect(moveX + 1000, moveY + 300, Me.Width / 8, Me.Height / 3))
        End Using

        buffer.Render(drawingVisual)
    End Sub

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Me.WindowState = WindowState.Maximized

    End Sub

    Private Sub Background_Loaded(sender As Object, e As RoutedEventArgs) Handles Background.Loaded
        While IsRunning
            TickCounter()

            If Keyboard.IsKeyDown(Key.Up) Then
                moveY += 5 '1'
            ElseIf Keyboard.IsKeyDown(Key.Down) Then
                moveY -= 5 '1'
            ElseIf Keyboard.IsKeyDown(Key.Left) Then
                moveX += 5 '1'
            ElseIf Keyboard.IsKeyDown(Key.Right) Then
                moveX -= 5 '1'
            End If

            DoEvents()
            Drawing()

        End While

    End Sub

    Private Sub TickCounter()
        If tSec = TimeOfDay.Second And IsRunning = True Then
            tTicks = tTicks + 1
        Else
            MaxTicks = tTicks
            tTicks = 0
            tSec = TimeOfDay.Second
        End If

    End Sub

    '<Do Events>'
    <SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)>
    Public Sub DoEvents()
        Dim frame As New DispatcherFrame()
        Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, New DispatcherOperationCallback(AddressOf ExitFrame), frame)
        Dispatcher.PushFrame(frame)
    End Sub

    Public Function ExitFrame(ByVal f As Object) As Object
        CType(f, DispatcherFrame).Continue = False

        Return Nothing
    End Function
    '</Do Events>'

    Private Sub MainWindow_Closed(sender As Object, e As EventArgs) Handles Me.Closed
        IsRunning = False
        Me.Close()
    End Sub

    Private Sub MoveDownButton_Click(sender As Object, e As RoutedEventArgs) Handles MoveDownButton.Click
        moveY -= 5
    End Sub

End Class

如果有人可以通过其他方法在按键上获得流畅的动画游戏地图,请告诉我。我目前正在使用一台 4 岁的东芝笔记本电脑,但我已经在较新的计算机上运行了此代码,并且存在相同的问题。我希望这个游戏可以在任何能够运行 WPF 应用程序的计算机上运行。

【问题讨论】:

  • WPF 已经提供了动画(例如通过故事板)。用那个。让图书馆担心帧率。您可以通过结合使用情节提要与视图模型中的属性绑定来连接到该系统。
  • 感谢马特的建议。我曾在游戏的不同部分使用故事板制作短动画或周期性动画。我假设任何故事板动画都容易出现 CompositionTarget.Rendering 事件的问题。我将尝试使用另一个测试项目中的 keydown 事件处理程序触发的情节提要动画来更新地图位置。

标签: wpf


【解决方案1】:

我只是在这里吐口水,因为存在大量潜在问题,并且试图解决看不见的问题充其量只是猜测工作,但话虽如此,我有一些您可能想深入研究的领域进一步思考:

  1. 如果帧速率不一致,您能否确定发生这种不一致的具体时间点? IE。大量动画同时运行时,或使用特定画笔时?

    • 您是否使用任何分析工具来查找代码的“热”部分?这可能有助于确定发生这些不一致的特定场景。
  2. 所讨论的动画以及可能同时运行的其他动画的 DesiredFrameRate 是多少?默认情况下,这是 60fps,这可能会很费力,尤其是当屏幕上有很多对象或正在轮询转换时。

    • 首先尝试在您的搭便车动画上设置Timeline.DesiredFrameRate=60
    • 其次,尝试将其他时间线的 DesiredFrameRate 设置为 30,以观察这是否会提高地图的性能 - 这会避开导致性能影响的其他元素的数量或质量。
  3. 您是否查看过用于地图和视觉对象的画笔的渲染质量?

    • 尝试降低画笔的渲染质量:RenderOptions.SetBitmapScalingMode(myBrush, BitmapScalingMode.LowQuality);
    • 确保缓存可重复使用的画笔:RenderOptions.SetCachingHint(myBrush, CachingHint.Cache);

有很多潜在的性能瓶颈,如果没有看到代码,除了这些松散的建议之外,很难为您提供更多。作为基线优化的快速第一站,有一个相当不错的comprehensive post by Kevin Pfister,它可以提供一些更有用的见解来代替任何代码或特定的已识别问题区域。

如果提供了更多信息,例如:

  • 您的硬件设置包括哪些内容,以及此问题是否在其他(更强大的)机器上仍然存在。
  • 如果您将渲染推迟到 GPU,那么 GPU 究竟是什么。
  • 您的目标是什么架构和平台。 IE。这是 Xamarin 项目、Win32WPF 还是其他?
  • 无论您是否仅在启用调试符号时遇到此问题。

我也许可以给你更多的建议。

【讨论】:

  • 我已经为覆盖 OnRender 方法的测试项目添加了 MainWindow 文件。我还不能使用时间线属性,因为我没有使用情节提要来制作动画。接下来我会按照 Matt Burland 的建议尝试这个。
  • 啊,很遗憾,在这种情况下,我无法提供任何额外的帮助,因为我没有在 WPF 中不使用提供的动画库的经验。
  • 没关系,JC。事实证明,使用故事板会导致同样的问题。我目前正在研究使用用于 WPF 的 OpenTK 控件来呈现游戏图形。就我而言,可以避免空域问题,因为 WPF 用户控件与游戏的游戏地图部分是分开的。尽管还有另一个名为 SharpGL 的 C# 库,它显然具有 WPF 控件,以某种方式避免了空域问题。我将发布更新,让大家知道结果如何。谢谢。
  • 酷,感谢 t0mr 的更新。有兴趣看看结果如何!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
  • 2020-07-17
  • 1970-01-01
  • 1970-01-01
  • 2013-02-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多