65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF:魔方

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (112投票s)

2012 年 2 月 1 日

CPOL

3分钟阅读

viewsIcon

192857

downloadIcon

10329

一个 WPF 3D 魔方应用程序

引言

我是魔方的忠实粉丝,几乎每天都要玩好几次。自从我有了第一个魔方以来,它就深深地吸引了我,也让我不断尝试加快解魔方的速度,并让我感到沮丧 - 我目前的平均速度是 17 秒。因此,我尝试制作一个 WPF 版本的魔方是很自然的事。

背景

WPF 中的 3D API 并非旨在提供全功能的游戏开发能力,但开发像魔方应用程序这样“简单”的东西是可行的。在本文中,我不会深入探讨 WPF 3D 功能的细节,因此我希望您熟悉 WPF 的这一部分。您应该熟悉的另一件事是基本的魔方术语。以防万一,这是你如何表示魔方的各个面,

322872/Cube_Faces.png

使用应用程序

要旋转魔方的一层,请按住鼠标左键并朝您想要旋转该层的方向滑动。一旦您松开鼠标左键,该层就会按预期旋转。

逆时针旋转 U 层

同样的原理也适用于整个魔方的旋转,但您使用鼠标右键而不是鼠标左键。

请注意,要进行旋转,您只需要与魔方的 F 面和 R 面交互。 要打乱魔方,请单击“Scramble(打乱)”按钮。

设计 & 布局

手动编码 XAML 设计魔方的 3D 模型将是一件繁琐的事情,因此我选择在 Blender 中对其进行建模。

322872/Blender_Screenshot_1.png

魔方建模进行中

322872/Blender_Screenshot_2.png

最终结果

在 Blender 中对魔方进行建模后,我将 3D 模型导出为 Wavefront (.obj) 文件,然后将其与相应的材质 (.mtl) 文件一起添加到 Expression Blend 的 WPF 项目中。

材质文件和对象文件

将材质和对象文件添加到项目后,只需右键单击对象文件并从上下文菜单中选择插入,即可将 3D 模型添加到 WPF 容器中。

对于此项目,我添加了更多灯光以充分照亮魔方,因此 Viewport3D 包含多个 PointLight、一个 DirectionalLight 和一个 AmbientLight

Viewport3D 被几个用于处理鼠标事件的 Path 屏蔽。 我可以选择使用 ModelUIElement3D - 它支持输入、焦点和事件 - 但我选择坚持使用 ModelVisual3D 对象。

每个代表魔方块的 ModelVisual3D 对象的命名都根据其“贴纸”的颜色命名,例如,WGR_Cubie 是白色-绿色-红色角块。

代码

该项目包含三个枚举。PieceLocations 枚举表示魔方块在 3D 空间中的位置。

Public Enum PieceLocations
    ' U layer locations
    FUL
    FU
    FUR
    RU
    BUR
    BU
    BUL
    LU
    UC
    ' E layer locations
    FL
    FC
    FR
    RC
    BR
    BC
    BL
    LC
    ' D layer locations
    FDL
    FD
    FDR
    RD
    BDR
    BD
    BDL
    LD
    DC
End Enum

Notations 枚举表示魔方的符号。

Public Enum Notations
    F
    F_prime
    F2
    B
    B_prime
    B2
    U
    U_prime
    U2
    D
    D_prime
    D2
    R
    R_prime
    R2
    L
    L_prime
    L2
End Enum

FaceNotations 枚举表示 Path 在魔方表面上的位置。 请记住,只有魔方的两个面被 Path 屏蔽。

Public Enum FaceLocations
    FUL
    FU
    FUR
    RUF
    RU
    RUB
    FL
    FC
    FR
    RF
    RC
    RB
    FDL
    FD
    FDR
    RDF
    RD
    RDB
End Enum

FacePath 定义了一个附加属性,该属性设置在屏蔽 Path 上。

Public Class FacePath
    Public Shared ReadOnly LocationProperty As DependencyProperty =
        DependencyProperty.RegisterAttached("Location", GetType(FaceLocations), GetType(FacePath),
                                            New FrameworkPropertyMetadata(FaceLocations.FC,
                                                                          FrameworkPropertyMetadataOptions.None))

    Public Shared Sub SetLocation(ByVal element As Path, ByVal value As FaceLocations)
        element.SetValue(LocationProperty, value)
    End Sub

    Public Shared Function GetLocation(ByVal element As Path) As FaceLocations
        Return CType(element.GetValue(LocationProperty), FaceLocations)
    End Function
End Class

Viewport3D 中的每个魔方块都与一个 CubePiece 类的对象相关联,该对象包含用于围绕特定轴旋转相关 ModelVisual3D 的方法。

Public Class CubePiece
    Public piece As ModelVisual3D

    Private axisPoint As New Point3D(0, 0, 0)
    Private axisAngleRtn3D As New AxisAngleRotation3D(New Vector3D(0, 0, 1), 0)
    Private dblAnimation As DoubleAnimation
    Private rotateTx3D As RotateTransform3D
    Private tx3dGroup As New Transform3DGroup

    Private Const ROTATION_TIME As Double = 200
    Friend Property PieceLocation() As PieceLocations

    Private Sub RotateAroundAxis(ByVal angle As Double)
        rotateTx3D = New RotateTransform3D(axisAngleRtn3D, axisPoint)
        dblAnimation = New DoubleAnimation(CDbl(angle), TimeSpan.FromMilliseconds(ROTATION_TIME),
                                           FillBehavior.HoldEnd)
        axisAngleRtn3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, dblAnimation)
        tx3dGroup.Children.Add(rotateTx3D)
        piece.Transform = tx3dGroup
    End Sub

    ''' <summary>
    ''' Rotate cube piece around the X-axis
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90° or 90°</param>
    Public Sub RotateAround_X_axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(1, 0, 0), 0)
        RotateAroundAxis(angle)
        ChangeLocationOnXaxisRotation(angle)
    End Sub

    ''' <summary>
    ''' Rotate cube piece around the Y-axis
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90° or 90°</param>
    Public Sub RotateAround_Y_axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(0, 1, 0), 0)
        RotateAroundAxis(angle)
        ChangeLocationOnYaxisRotation(angle)
    End Sub

    ''' <summary>
    ''' Rotate cube piece around the Z-axis
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90° or 90°</param>
    Public Sub RotateAround_Z_axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(0, 0, 1), 0)
        RotateAroundAxis(angle)
        ChangeLocationOnZaxisRotation(angle)
    End Sub
...
End Class

Scrambler 包含用于生成打乱步骤和打乱魔方的方法。

Public Class Scrambler
    Private index As Integer
    Private isScrambling As Boolean

    Private Const CLOCKWISE As Double = -90
    Private Const ANTI_CLOCKWISE As Double = 90

    Private mainWin As MainWindow
    Private rnd As Random
    Private scrambleTimer As DispatcherTimer
    Private scramble As List(Of Notations)

    Public Sub New(ByRef win As MainWindow)
        mainWin = win
        rnd = New Random
        scramble = New List(Of Notations)
        scrambleTimer = New DispatcherTimer
        scrambleTimer.Interval = New TimeSpan(0, 0, 0, 0, 400)
        AddHandler scrambleTimer.Tick, AddressOf Timer_Tick
    End Sub

    Public Sub ScrambleCube()
        If isScrambling = False Then
            scramble.Clear()
            ' Create a scramble with 20 values
            For i As Integer = 0 To 19
                AddToScramble()
            Next
            isScrambling = True
            scrambleTimer.Start()
            mainWin.CubeGroupGrid.IsEnabled = False
        Else
            MessageBox.Show("Scrambling of cube already in progress", _
                            "WPF Rubiks", MessageBoxButton.OK, MessageBoxImage.Exclamation)
        End If
    End Sub

    ''' <summary>
    ''' Create scramble with dissimilar values following each other, and the new value 
    ''' in the List is not an inverse or double of the preceding value e.g.
    ''' F' does not come after F, or F2 after F.
    ''' </summary>
    Private Sub AddToScramble()
        Dim randomNum As Integer = rnd.Next(0, 18)
        Dim newNotation As String = [Enum].GetName(GetType(Notations), randomNum)
        Dim notation As Notations

        If (scramble.Count > 0) Then
            Dim lastItem As String = scramble(scramble.Count - 1).ToString

            If newNotation.Contains("F") AndAlso lastItem.Contains("F") Then
                AddToScramble()
            ElseIf newNotation.Contains("R") AndAlso lastItem.Contains("R") Then
                AddToScramble()
            ElseIf newNotation.Contains("B") AndAlso lastItem.Contains("B") Then
                AddToScramble()
            ElseIf newNotation.Contains("L") AndAlso lastItem.Contains("L") Then
                AddToScramble()
            ElseIf newNotation.Contains("U") AndAlso lastItem.Contains("U") Then
                AddToScramble()
            ElseIf newNotation.Contains("D") AndAlso lastItem.Contains("D") Then
                AddToScramble()
            Else
                notation = [Enum].Parse(GetType(Notations), newNotation)
                scramble.Add(notation)
            End If
        Else
            notation = [Enum].Parse(GetType(Notations), newNotation)
            scramble.Add(notation)
        End If
    End Sub

    Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
        If (index < 19) Then
            RotateCubePieces(index)
            index += 1
        Else
            isScrambling = False
            scrambleTimer.Stop()
            index = 0
            mainWin.CubeGroupGrid.IsEnabled = True
        End If
    End Sub
...
End Class

该项目包含一些其他类,这些类主要用于确定应旋转魔方的一层还是整个魔方的方向。 您可以通过从文章页面顶部的下载链接下载项目源文件来查看这些类。

结论

我希望您能玩得开心,解开这个 WPF 魔方。 我已经多次打乱和解开了它,它和真实的魔方一样令人兴奋。

历史

  • 2012 年 2 月 1 日:首次发布
  • 2014 年 9 月 25 日:更新文章和源代码
© . All rights reserved.