首页 > 攻略 > 游戏问答 > 详情

实现《英雄联盟》 PLAY 按键

2024-06-12 13:39:40 | 来源: 互联网整理

实现《英雄联盟》 PLAY 按键

这篇文章是对WPF RiotPlayButton教程视频的技术回顾。

摘要

本文详细介绍并分析了使用纯WPF技术开发受《英雄联盟》游戏启发的PLAY按钮的过程。本文强调了利用WPF功能创建各种用户界面组件的过程,并为开源开发提供了新的视角。同时,探索了动画和触发器等高级WPF功能,以提升用户交互体验。

介绍

用户界面组件在提升用户体验方面非常重要。在游戏中,反应迅速且视觉上有吸引力的PLAY按钮是通往娱乐世界的门户。本文展示了使用WPF这一构建丰富桌面应用程序的强大框架创建PLAY按钮的过程。

项目背景

本文讨论的项目尽可能全面地展示了WPF技术的能力。几年前发布这个项目后,获得了积极的反馈,这激励我继续为开源开发做贡献。随着.NET技术的发展,我不断更新和改进共享在GitHub上的代码。考虑到整个项目中包含的内容丰富,我决定详细分析每个部分的组成和技术重点,希望能为更多喜欢WPF技术的人提供帮助。

按钮构成

通过分析器可以看到,这个PLAY按钮继承了WPF ToggleButton的属性。左边是《英雄联盟》游戏的标志,右边包含边界、图像、文本等多种设计元素。此外,还添加了鼠标悬停和检查触发效果。

主要内容分析

1. 创建不规则形状

第一个和第二个图形可以通过使用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控件的主要属性如下:

  • Data 属性:Data 属性是 Path 控件的重要属性,用于指定描述形状轮廓的路径数据。路径数据格式包括MoveTo (M)、LineTo (L)、CurveTo (C)、ClosePath (Z)等命令与坐标的结合。
  • Fill 属性:Fill属性用于指定形状内部的填充颜色,可以使用颜色、渐变、图案或透明度填充形状内部。
  • Stroke 属性:Stroke属性用于指定形状轮廓的颜色,可以使用各种颜色定义轮廓颜色。
  • StrokeThickness 属性:StrokeThickness属性用于指定轮廓的厚度,决定轮廓的宽度。
  • 命令和坐标:路径数据是命令和坐标的连续体,这些命令指示WPF从一个点到另一个点如何绘制形状。常见的路径命令包括:

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)。

2. 创建渐变颜色

<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来自定义颜色。

LinearGradientBrush 的主要属性及使用方法

  • StartPoint 和 EndPoint:StartPoint指定渐变的起点,通常使用相对坐标表示。这里 (0, 0) 是左上角,(1, 1) 是右下角。EndPoint使用相对坐标指定渐变的终点。
  • GradientStops:GradientStops是GradientStop对象的集合,每个对象定义颜色和相对位置(Offset)。GradientStop的Color属性定义指定位置的颜色,Offset属性定义渐变中的颜色位置,通常在0到1之间。
  • 渐变方向:渐变的方向由StartPoint和EndPoint决定。例如,StartPoint为(0, 0),EndPoint为(1, 1)时,渐变从左上角到右下角。
  • 渐变类型:LinearGradientBrush默认设置为线性渐变,颜色沿直线渐变。通过调整StartPoint和EndPoint,可以改变渐变的方向和起点,生成各种渐变效果。

在这个项目中,我们希望创建一个从形状中央开始向下移动的垂直渐变。因此,将StartPoint设置为(0.5, 0)指定渐变的起点为顶部中央,将EndPoint设置为(0.5, 1)指定渐变的终点为底部中央。

然后,GradientStops集合包含三个GradientStop对象,每个对象定义了不同的颜色和相对位置:

  • 第一个GradientStop:Color设置为#CC3FE7EE。Offset设置为0,表示该颜色位于渐变的起点。

  • 第二个GradientStop:Color设置为#CC006D7D。Offset设置为0.5,表示该

颜色位于渐变的中间。

  • 第三个GradientStop:Color设置为#CC0493A7。Offset设置为 1,表示该颜色位于渐变的终点。
  1. 处理Path和Border厚度

在Border控件中:

Border控件的边框线包含在Border内。边框线的厚度由BorderThickness属性控制,以设备独立像素(DIPs)指定边框线的宽度。

在Path控件中:

Path控件的边框线以StrokeThickness属性的中心位置为基准绘制。StrokeThickness 控制边框线的厚度,表示边框线从中心延伸的距离。

在这个固定大小的图形中,将Border和Path的厚度都设置为2,边距设置为4 4 4 4。但在这种设置下,可以看到Path的上边框超出了Border。因此,需要根据StrokeThickness调整Path的边距。左边距已经设置为40,可以覆盖GreenLine,所以没有问题。上边距需要增加1像素,设置为5像素,右边距和下边距不需要更改。由于Path的尺寸固定为118x28,只需调整左边和上边的边距。

此外,上边距增加5像素后,下边看起来可能会被裁剪。为避免这种情况,可以将下边距设置为-5像素,这样上边增加的5像素会被去掉,布局会保持平衡。另一种方法是保持下边距为0像素。这两种方法都可以通过增加上边距来防止下边被裁剪。

4. 使用 Jamesnet.WPF Nuget 生成动画

在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包。只需添加这个包,就可以轻松地使用和编写动画。

5. 使用 Grid.Clip 属性

<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

综上所述,在这种情况下使用二次贝塞尔曲线更为合适。

沟通与支持

我们随时保持沟通渠道开放。大家可以通过以下方式与我们互动:

  • GitHub[4]: 关注、Fork、Stars
  • BiliBili[5]: 一键三连
  • 邮箱: james@jamesnet.dev

参考资料

[1]

源码链接: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

热门手游排行榜