2024-06-12 13:39:40 | 来源: 互联网整理
实现《英雄联盟》 PLAY 按键
这篇文章是对WPF RiotPlayButton教程视频的技术回顾。
本文详细介绍并分析了使用纯WPF技术开发受《英雄联盟》游戏启发的PLAY按钮的过程。本文强调了利用WPF功能创建各种用户界面组件的过程,并为开源开发提供了新的视角。同时,探索了动画和触发器等高级WPF功能,以提升用户交互体验。
用户界面组件在提升用户体验方面非常重要。在游戏中,反应迅速且视觉上有吸引力的PLAY按钮是通往娱乐世界的门户。本文展示了使用WPF这一构建丰富桌面应用程序的强大框架创建PLAY按钮的过程。
本文讨论的项目尽可能全面地展示了WPF技术的能力。几年前发布这个项目后,获得了积极的反馈,这激励我继续为开源开发做贡献。随着.NET技术的发展,我不断更新和改进共享在GitHub上的代码。考虑到整个项目中包含的内容丰富,我决定详细分析每个部分的组成和技术重点,希望能为更多喜欢WPF技术的人提供帮助。
通过分析器可以看到,这个PLAY按钮继承了WPF ToggleButton的属性。左边是《英雄联盟》游戏的标志,右边包含边界、图像、文本等多种设计元素。此外,还添加了鼠标悬停和检查触发效果。
第一个和第二个图形可以通过使用Border控件轻松编码。然而,第三个尖角和弧形的图形不能简单地使用Border编码。因此,尽管最初可以使用多边形和坐标来绘制,但多边形属性不提供绘制弧形的功能。因此,必须使用Path控件来编码。
<Style TargetType="{x:Type Path}" x:Key="Arrow"> <Setter Property="Fill" Value="#1E2328"/> <Setter Property="Stroke" Value="{StaticResource ArrowStroke}"/> <Setter Property="StrokeThickness" Value="2"/> <Setter Property="Data" Value="M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 Z"/> <Setter Property="Margin" Value="40 5 4 -5"/> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect BlurRadius="5" ShadowDepth="2"/> </Setter.Value> </Setter> </Style>
在WPF中,Path控件是用于绘制各种形状和轮廓的强大工具。Path控件通过路径数据定义形状,路径数据是一系列指定如何绘制形状的命令和坐标。
Path控件的主要属性如下:
M (MoveTo):将绘制点移动到指定坐标。
L (LineTo):绘制到指定坐标的直线。
C (CurveTo):使用控制点绘制贝塞尔曲线。
Z (ClosePath):关闭路径,将当前点连接到起点形成闭合形状。
Data属性是Path控件的关键属性,包含定义形状轮廓的路径数据。这些路径数据由一系列命令和坐标组成,描述了路径的轮廓。在这个项目中,路径数据的命令和坐标的详细描述如下:
可以将其简单地解释为X/Y坐标轴。将此形状的长度设置为118,宽度设置为28:
M 0,0:这是“MoveTo”命令,将绘制点移动到坐标(0, 0)作为起点。
L 103,0:这是“LineTo”命令,从当前点(0, 0)绘制到坐标(103, 0)的直线。接着绘制到 (118, 14)、(103, 28)、(0, 28)的线段。
由于这是对称形状,第二条线的Y坐标是形状总高度的一半,即14。
接下来是绘制曲线的部分:C 10,14 0,0 0,0 z:这是 “贝塞尔曲线” 命令,定义了前一个点为控制点,后一个点为终点的贝塞尔曲线。这个命令定义了控制点为(10, 14),终点为(0, 0)的贝塞尔曲线,并使用‘z’命令关闭路径,将其连接回起点(0, 0)。
<LinearGradientBrush x:Key="ArrowStroke" StartPoint="0.5,0" EndPoint="0.5,1" > <GradientStop Color="#CC3FE7EE" Offset="0"/> <GradientStop Color="#CC006D7D" Offset="0.5"/> <GradientStop Color="#CC0493A7" Offset="1"/> </LinearGradientBrush> <LinearGradientBrush x:Key="ArrowStrokeOver" StartPoint="0.5,0" EndPoint="0.5,1" > <GradientStop Color="#FFAFF5FF" Offset="0"/> <GradientStop Color="#FF46E6FF" Offset="0.5"/> <GradientStop Color="#FF00ADD4" Offset="1"/> </LinearGradientBrush> <LinearGradientBrush x:Key="ArrowFillOver" StartPoint="0.5,0" EndPoint="0.5,1" > <GradientStop Color="#FF1D3B4A" Offset="0"/> <GradientStop Color="#FF082734" Offset="1"/> </LinearGradientBrush>
在游戏的这个部分,描边部分并不是简单的纯色,而是由多种色调组成的渐变颜色。为了实现这个效果,我们可以使用LinearGradientBrush来自定义颜色。
在这个项目中,我们希望创建一个从形状中央开始向下移动的垂直渐变。因此,将StartPoint设置为(0.5, 0)指定渐变的起点为顶部中央,将EndPoint设置为(0.5, 1)指定渐变的终点为底部中央。
然后,GradientStops集合包含三个GradientStop对象,每个对象定义了不同的颜色和相对位置:
第一个GradientStop:Color设置为#CC3FE7EE。Offset设置为0,表示该颜色位于渐变的起点。
第二个GradientStop:Color设置为#CC006D7D。Offset设置为0.5,表示该
颜色位于渐变的中间。
Border控件的边框线包含在Border内。边框线的厚度由BorderThickness属性控制,以设备独立像素(DIPs)指定边框线的宽度。
Path控件的边框线以StrokeThickness属性的中心位置为基准绘制。StrokeThickness 控制边框线的厚度,表示边框线从中心延伸的距离。
在这个固定大小的图形中,将Border和Path的厚度都设置为2,边距设置为4 4 4 4。但在这种设置下,可以看到Path的上边框超出了Border。因此,需要根据StrokeThickness调整Path的边距。左边距已经设置为40,可以覆盖GreenLine,所以没有问题。上边距需要增加1像素,设置为5像素,右边距和下边距不需要更改。由于Path的尺寸固定为118x28,只需调整左边和上边的边距。
此外,上边距增加5像素后,下边看起来可能会被裁剪。为避免这种情况,可以将下边距设置为-5像素,这样上边增加的5像素会被去掉,布局会保持平衡。另一种方法是保持下边距为0像素。这两种方法都可以通过增加上边距来防止下边被裁剪。
在WPF中,可以生成各种动态动画来使用户界面更加有趣。在这个项目中,使用厚度动画为TextBlock的文本部分添加有趣的动画效果。
<Application x:Class="VickyPlayButton.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:james="https://jamesnet.dev/xaml/presentation" StartupUri="MainWindow.xaml"> <Application.Resources> <Style TargetType="{x:Type ToggleButton}"> <Setter Property="Height" Value="38"/> <Setter Property="Width" Value="165"/> <Setter Property="Foreground" Value="#FFFFFF"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ToggleButton}"> <ControlTemplate.Resources> <Storyboard x:Key="Checked"> <james:ThickItem Mode="CubicEaseInOut" TargetName="play" Property="Margin" Duration="0:0:0:0.5" To="30 100 0 0"/> <james:ThickItem Mode="CubicEaseInOut" TargetName="stop" Property="Margin" Duration="0:0:0:0.5" To="30 0 0 0"/> </Storyboard> <Storyboard x:Key="UnChecked"> <james:ThickItem Mode="CubicEaseInOut" TargetName="play" Property="Margin" Duration="0:0:0:0.5" To="30 0 0 0"/> <james:ThickItem Mode="CubicEaseInOut" TargetName="stop" Property="Margin" Duration="0:0:0:0.5" To="30 0 0 100"/> </Storyboard> </ControlTemplate.Resources> <Grid Background="{TemplateBinding Background}"> <Border Style="{StaticResource GoldLine}"/> <Image Style="{StaticResource Emblem}"/> <Border Style="{StaticResource GreenLine}"/> <Path x:Name="path" Style="{StaticResource Arrow}"/> <Grid> <Grid.Clip> <RectangleGeometry Rect="0,5,165,28"/> </Grid.Clip> <TextBlock x:Name="play" Style="{StaticResource Play}"/> <TextBlock x:Name="stop" Style="{StaticResource Stop}"/> </Grid> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="path" Property="Fill" Value="{StaticResource ArrowFillOver}"/> <Setter TargetName="path" Property="Stroke" Value="{StaticResource ArrowStrokeOver}"/> <Setter Property="Foreground" Value="#FFFCF1DC"/> <Setter Property="Cursor" Value="Hand"/> </Trigger> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="path" Property="Fill" Value="#1E2328"/> <Setter TargetName="path" Property="Stroke" Value="#5C5B57"/> <Setter Property="Foreground" Value="#3C3C41"/> <Trigger.EnterActions> <BeginStoryboard Storyboard="{StaticResource Checked}"/> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard Storyboard="{StaticResource UnChecked}"/> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Application.Resources> </Application>
动画可以通过ControlTemplate.Resources定义,定义了“Checked”和“UnChecked”两个动画资源。在“Checked”状态下,“Play”文本消失,“Stop”文本出现;在“UnChecked”状态下,“Stop”文本消失,“Play”文本出现。这就创建了一个翻转效果的动画。
为了更容易地创建和使用动画,我将各种WPF动画编译成了Jamesnet.WPF Nuget包。只需添加这个包,就可以轻松地使用和编写动画。
<Grid Background="{TemplateBinding Background}"> <Border Style="{StaticResource GoldLine}"/> <Image Style="{StaticResource Emblem}"/> <Border Style="{StaticResource GreenLine}"/> <Path x:Name="path" Style="{StaticResource Arrow}"/> <Grid> <Grid.Clip> <RectangleGeometry Rect="0,5,165,28"/> </Grid.Clip> <TextBlock x:Name="play" Style="{StaticResource Play}"/> <TextBlock x:Name="stop" Style="{StaticResource Stop}"/> </Grid> </Grid>
由于Grid内的元素相互重叠,在创建文本上下滚动动画时,可能会出现文本超出边框的视觉问题。为了解决这个问题,使用<Grid.Clip>属性。
<Grid.Clip>是一个XAML元素,用于定义子元素的可见区域。剪辑区域通常是一个矩形,只有剪辑区域内的内容才会显示,超出部分将被隐藏。
在这个项目中,<Grid.Clip>区域设置在Path的尺寸范围内:Rect="0,5,165,28"。这样,文本只会在这个区域内显示,从而在Path内实现上下滚动的效果。
最近有一位观众指出了我们视频中的一个错误,我在此做出更正和说明。
11:34贝塞尔曲线里C不是弧线的中点吧,控制点是在线外边的
“贝塞尔曲线中,C点不是曲线的中点。控制点是在曲线外的。”
在11:34的视频里,会看到以下内容(图片与视频说明一致)。
经过重新审视,我发现确实在解释贝塞尔曲线时出现了误解。感谢这位观众的指正,现在我来进行修正。
首先,视频中使用的路径如下:
M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 Z
Y轴的0,0基准反转对理解没有太大影响,请大家谅解。
通过生成的图表可以看到,C (10, 14)控制点的位置实际上在曲线的外部,而不是中点。这正是观众指出的问题。
接下来,我们详细了解一下三次贝塞尔曲线的机制。
三次贝塞尔曲线需要4个点:P0, P1, P2, P3。根据当前的几何路径数据,分别映射如下:
P0: 0,28 (起点)
P1: 10,14 (控制点1)
P2: 0,0 (控制点2)
P3: 0,0 (终点)
此外,我们绘制了曲线随时间变化的过程。
蓝色线段的起点和终点随着控制点的移动而绘制出曲线。具体的可视化效果请参见这里[3],该博客中有很好的动画说明。
在这种情况下,使用二次贝塞尔曲线比三次贝塞尔曲线更合适,因为二次贝塞尔曲线需要的控制点更少。
三次贝塞尔曲线 (Cubic Bezier)
M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 Z
二次贝塞尔曲线(Quadratic Bezier)
M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 Q 10,14 0,0 Z
综上所述,在这种情况下使用二次贝塞尔曲线更为合适。
我们随时保持沟通渠道开放。大家可以通过以下方式与我们互动:
源码链接:https://github.com/vickyqu115/riotplaybutton
[2]教学视频:https://bit.ly/3xI9DNh
[3]这里:https://blog.naver.com/kyuniitale/40022945907
[4]GitHub:https://github.com/vickyqu115/smartdate
[5]BiliBili:https://bit.ly/3xI9DNh
热门手游排行榜