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

VB.NET精灵动画系统性能卡顿问题优化求助

精灵动画系统性能优化方案

问题背景

开发的精灵动画系统中,SpriteAnimator负责帧切换控制,DeltaTimeCalculations生成每帧事件。每个游戏对象需实例化SpriteAnimator承载图像并接收帧信号,但多实例场景下无论单/多DeltaTimeCalculations实例,均出现严重卡顿,功能正常但性能不足。

现有代码

DeltaTimeCalculations 类

Imports System
Imports System.Threading.Tasks

Public Class DeltaTimeCalculations
    Private ParentObject As Object
    Private LoopTimer1 As DateTime = DateTime.Now
    Private LoopTimer2 As DateTime = DateTime.Now

    Public Async Function UpdateDeltaTime() As Task
        While True
            Await Task.Delay(1)
            LoopTimer2 = DateTime.Now
            Dim DeltaTime As Decimal = (LoopTimer2.Ticks - LoopTimer1.Ticks) / 10000000
            ParentObject.DeltaTimeTick(DeltaTime)
            LoopTimer1 = LoopTimer2
        End While
    End Function

    Sub New(ReceivedParent As Object)
        ParentObject = ReceivedParent
        UpdateDeltaTime()
    End Sub
End Class

SpriteAnimator 类

Public Class SpriteAnimator
Public Shared MainWindowClass As MainWindow

Public VisualInstance As Image
Public VisualContainerTranslateTransform As New TranslateTransform
Public VisualInstanceTranslateTransform As New TranslateTransform
Public VisualTranslateTransformName As String
Public CurrentCropRect As Int32Rect
Public CurrentBitmap As New BitmapImage

Private Loopable As Boolean = False
Private LoopStart As Decimal = 0
Private LoopEnd As Decimal = 0
Private FrameCount As Integer = 0
Private FrameTimer As Decimal = 0
Private FrameInterval As Decimal = 0
Private FrameKeepup As Decimal = 0
Private CurrentFrameCount As Integer = 0
Private TicksEnabled As Boolean = False
Private SizeX As Decimal = 0
Private SizeY As Decimal = 0

Private CurrentSheetRows As Integer = 0
Private CurrentSheetColumns As Integer = 0
Private XIndex As Integer = 0
Private YIndex As Integer = 0

Public Shared Event TickHandleEvent()

Sub New(ReceivedMainWindowClass As MainWindow, ReceivedZIndex As Integer, ReceivedObjectName As String, ReceivedTranslateTransformName As String, ReceivedStartX As Decimal, ReceivedStartY As Decimal)
    MainWindowClass = ReceivedMainWindowClass
    VisualInstance = New Image With {
            .HorizontalAlignment = HorizontalAlignment.Left,
            .VerticalAlignment = VerticalAlignment.Top,
            .Stretch = Stretch.Fill,
            .StretchDirection = StretchDirection.Both,
            .SnapsToDevicePixels = True,
            .RenderTransform = VisualContainerTranslateTransform,
            .Width = 0,
            .Height = 0,
            .Name = ReceivedObjectName
        }
    VisualTranslateTransformName = ReceivedTranslateTransformName
    VisualContainerTranslateTransform.X = ReceivedStartX
    VisualContainerTranslateTransform.Y = ReceivedStartY
    VisualInstanceTranslateTransform.X = 0
    VisualInstanceTranslateTransform.Y = 0

    MainWindowClass.SetGridIndex(VisualInstance, ReceivedZIndex)
    MainWindowClass.RegisterNameToGrid(VisualTranslateTransformName, VisualInstanceTranslateTransform)
    MainWindowClass.AddNewUIChildThing(VisualInstance)
End Sub
Public Sub Initiate(ReceivedRows As Integer, ReceivedColumns As Integer, ReceivedFrameCount As Integer, ReceivedSheetWidth As Decimal, ReceivedSheetHeight As Decimal, ReceivedSpriteWidth As Decimal, ReceivedSpriteHeight As Decimal, SheetUri As String, Loops As Boolean, ReceivedFrameTimer As Decimal, ReceivedZIndex As Integer)
    TicksEnabled = False
    RemoveHandler TickHandleEvent, AddressOf DeltaTimeHandler
    VisualInstance.Width = ReceivedSheetWidth
    VisualInstance.Height = ReceivedSheetHeight
    SizeX = ReceivedSpriteWidth
    SizeY = ReceivedSpriteHeight
    CurrentCropRect = New Int32Rect(0, 0, ReceivedSpriteWidth, ReceivedSpriteHeight)
    CurrentSheetRows = ReceivedRows - 1
    CurrentSheetColumns = ReceivedColumns - 1
    FrameCount = ReceivedFrameCount
    Dim NewBitmapImage As New BitmapImage
    NewBitmapImage.BeginInit()
    NewBitmapImage.UriSource = New Uri(SheetUri)
    NewBitmapImage.EndInit()
    CurrentBitmap = NewBitmapImage
    CurrentCropRect.X = 0
    CurrentCropRect.Y = 0
    Dim NewBitmap As New CroppedBitmap(CurrentBitmap, CurrentCropRect)
    VisualInstance.Source = NewBitmap
    VisualInstanceTranslateTransform.X = 0
    VisualInstanceTranslateTransform.Y = 0
    MainWindowClass.SetGridIndex(VisualInstance, ReceivedZIndex)
    If ReceivedFrameCount > 1 Then
        AddHandler TickHandleEvent, AddressOf DeltaTimeHandler
        TicksEnabled = True
    End If
End Sub
Public Sub DeltaTimeHandler()
    If TicksEnabled = True Then
        CurrentFrameCount += 1
        XIndex += 1
            If CurrentFrameCount >= FrameCount Then
                While CurrentFrameCount >= FrameCount
                    CurrentFrameCount -= FrameCount
                End While
                XIndex = CurrentFrameCount
            End If
            If XIndex > CurrentSheetColumns Then
                While XIndex > CurrentSheetColumns
                    XIndex = (XIndex - CurrentSheetColumns) - 1
                    YIndex += 1
                End While
            End If
            If YIndex > CurrentSheetRows Then
                While YIndex > CurrentSheetRows
                    YIndex = (YIndex - CurrentSheetRows) - 1
                End While
            End If
            CurrentCropRect.X = XIndex * SizeX
            CurrentCropRect.Y = YIndex * SizeY
            Dim NewBitmap As New CroppedBitmap(CurrentBitmap, CurrentCropRect)
            VisualInstance.Source = NewBitmap
    End If
End Sub

Public Shared Sub DeltaTimeTick()
    RaiseEvent TickHandleEvent()
End Sub
End Class

优化建议

1. 替换全局计时逻辑,消除高频无效调度

  • 移除DeltaTimeCalculations类,改用WPF内置DispatcherTimer作为全局唯一帧时钟,设置间隔与显示器刷新率匹配(如16ms对应60fps),避免Task.Delay(1)导致的毫秒级高频调度开销。
  • 全局仅维护一个定时器,通过共享事件触发所有动画更新,减少线程上下文切换。

示例实现:

' 全局时钟(可封装在MainWindow或独立GlobalClock类)
Private Shared ReadOnly FrameTimer As New DispatcherTimer()

Shared Sub New()
    FrameTimer.Interval = TimeSpan.FromMilliseconds(16) ' 60fps
    AddHandler FrameTimer.Tick, AddressOf OnGlobalFrameTick
    FrameTimer.Start()
End Sub

Private Shared Sub OnGlobalFrameTick(sender As Object, e As EventArgs)
    SpriteAnimator.DeltaTimeTick()
End Sub

2. 重构帧更新逻辑,减少计算与对象创建开销

  • 预缓存帧资源:在Initiate方法中提前生成所有帧的CroppedBitmap并存储到列表,帧更新时直接切换Source,避免每次新建对象:
    Private FrameCache As List(Of CroppedBitmap)
    
    ' 在Initiate方法中添加缓存逻辑
    FrameCache = New List(Of CroppedBitmap)()
    For i As Integer = 0 To FrameCount - 1
        Dim x = i Mod (CurrentSheetColumns + 1)
        Dim y = i \ (CurrentSheetColumns + 1)
        y = y Mod (CurrentSheetRows + 1)
        Dim cropRect = New Int32Rect(x * SizeX, y * SizeY, SizeX, SizeY)
        FrameCache.Add(New CroppedBitmap(CurrentBitmap, cropRect))
    Next
    
  • 简化索引计算:用取模和整除运算替代循环减法,大幅降低CPU占用:
    Public Sub DeltaTimeHandler()
        If Not TicksEnabled Then Return
    
        CurrentFrameCount = (CurrentFrameCount + 1) Mod FrameCount
        XIndex = CurrentFrameCount Mod (CurrentSheetColumns + 1)
        YIndex = CurrentFrameCount \ (CurrentSheetColumns + 1)
        YIndex = YIndex Mod (CurrentSheetRows + 1)
    
        ' 直接从缓存取帧
        VisualInstance.Source = FrameCache(CurrentFrameCount)
    End Sub
    

3. 优化WPF渲染与资源加载

  • 启用Bitmap缓存:加载纹理时设置CacheOption = BitmapCacheOption.OnLoad,避免重复从磁盘读取:
    NewBitmapImage.BeginInit()
    NewBitmapImage.UriSource = New Uri(SheetUri)
    NewBitmapImage.CacheOption = BitmapCacheOption.OnLoad
    NewBitmapImage.EndInit()
    
  • 优化渲染模式:根据动画风格设置BitmapScalingMode,像素风格动画用NearestNeighbor减少缩放计算:
    VisualInstance = New Image With {
        ' ...其他属性
        .RenderOptions.SetBitmapScalingMode(BitmapScalingMode.NearestNeighbor)
    }
    
  • 减少UI层级:简化VisualInstance的容器布局,避免不必要的嵌套,降低WPF布局计算开销。

4. 修复事件订阅与内存泄漏

  • 在SpriteAnimator销毁时主动移除事件订阅,避免无效事件触发和内存泄漏:
    Protected Overrides Sub Finalize()
        MyBase.Finalize()
        RemoveHandler TickHandleEvent, AddressOf DeltaTimeHandler
    End Sub
    
  • 确保Initiate方法中事件订阅的唯一性,避免重复绑定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 21:15:54