用WPF编写《英雄联盟》客户端操作界面 - 1.Play Button



摘要:
  本文提供了使用单纯只WPF技术开发类似游戏《英雄联盟》中的PLAY按键的详细内容讲解和分析。它突出了利用WPF功能创建多功能用户界面组件的过程,并提供了新的开源开发思路。文章还探讨了使用WPF的高级功能,如动画和触发器,以增强用户交互体验。

引言:
  用户界面组件在提升用户体验方面至关重要。在游戏中,一个反应灵敏且视觉吸引人的PLAY按钮是进入娱乐世界的门户。本文采用的方法展示了使用WPF创建播放按钮的过程,WPF为构建丰富的桌面应用提供了强大的框架。

项目背景:
  本文讨论的项目是一个旨在尽可能多的展示WP技术F优秀能力的开源项目。几年前我们发布了该项目,并受到了极大的积极反响,这样的动力也一直鼓励着我们继续进行开源贡献。在.Net技术迭代更新的同时,我们也在不断的更新和完善之前公布在GitHub上的代码。由于这个总项目涉及的内容较多,因此我们决定将其进行拆解,逐个详细分析每个部分的构成和技术重点,希望能为更多喜欢WPF技术的人学习过程中提供帮助。



按钮组成:


 
  通过分析器(我们独立开发的开源项目,目前暂未公布)我们可以看到这个PLAY按钮继承了WPFToggleButton的属性,左侧是一个英雄联盟游戏的Logo,右侧是一个由不同设计的Border、图片和文本组成的多元素图形,此外还添加了交互式mouseover和checked触发效果。



项目重点内容分析:

1.创建不规则形状:



  前两个图形我们可以简单的用Border进行编码。第三个图形是一个包括尖头和弧线的不规则图形,所以不能简单的使用Border进行编码。因此我们一开始可能会想到是有Polygon利用坐标进行绘制,但是Polygon属性并不能提供绘制弧线的功能,所以我们应该使用Path进行编码。

  具体分析: 在WPF中,Path 控件是一个非常强大的工具,用于绘制各种形状和轮廓。Path 控件使用路径数据(Path Data)来定义形状,路径数据包含一系列命令和坐标,以指定如何绘制形状。
 

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

它的基本属性有:
(1)Data 属性: Data 属性是 Path 控件的关键属性,它用于指定路径数据,即一系列的命令和坐标,来描述形状的轮廓。 路径数据的格式是一系列的命令,如 MoveTo(M)、LineTo(L)、CurveTo(C)、ClosePath(Z)等,配合坐标来定义形状。 通过在 Data 属性中提供路径数据,我们可以创建各种不同的形状,包括线段、曲线、多边形等。
(2)Fill 属性: Fill 属性用于指定形状内部的填充颜色。我们可以使用颜色、渐变、图案或透明度来填充形状的内部。

(3)Stroke 属性: Stroke 属性用于指定形状的轮廓颜色。你可以使用颜色来定义轮廓线的颜色。
(4)StrokeThickness 属性: StrokeThickness 属性用于指定轮廓线的粗细。它确定了轮廓线的宽度。

(5)命令和坐标: 路径数据由一系列的命令和坐标组成,这些命令告诉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) 的位置。接下来依次利用L绘制到(118,14),(103,28),( 0,28)的连续的直线图形。
*由于这是一个对成图形,因此第二个直线的坐标Y轴数值为总图形的高的一半:14。

  接下来是曲线的绘制部分: C 10,14 0,0 0,0 z: 这是一个"Bezier Curve"命令,它定义了一个贝塞尔曲线,前面的点是控制点,后面的点是终点。这个命令定义了一个贝塞尔曲线,其中控制点是 (10, 14),终点是 (0, 0),并且使用 z 命令将路径闭合,连接到起始点 (0, 0)。


2.创建渐变色:

  游戏中的这个部分的stroke部分并不是一个简单的纯色,而是由多个颜色混合的渐变色,为了打到这样的效果我们可以使用LinearGradientBrush来进行颜色的自定义。

  LinearGradientBrush 的主要属性和用法:

(1)StartPoint 和 EndPoint: StartPoint 属性指定了渐变的起始点,通常使用相对坐标来表示。坐标 (0, 0) 表示左上角,坐标 (1, 1) 表示右下角。 EndPoint 属性指定了渐变的结束点,也使用相对坐标来表示。

(2)GradientStops: GradientStops 是一个 GradientStop 对象的集合,每个对象定义了一个颜色和一个相对位置(Offset)。 GradientStop 对象的 Color 属性表示在相对位置指定的点的颜色。 GradientStop 对象的 Offset 属性定义了颜色在渐变中的位置,通常在 0 到 1 之间。

(3)渐变方向: 渐变的方向由 StartPoint 和 EndPoint 决定。例如,如果 StartPoint 是 (0, 0),EndPoint 是 (1, 1),则渐变会从左上角到右下角。

(4)渐变类型: LinearGradientBrush 默认为线性渐变(Linear Gradient),即颜色沿着一条直线过渡。 你还可以通过调整 StartPoint 和 EndPoint 来改变渐变的方向和起始点,从而创建不同方向和形状的渐变效果。

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


这个项目中我希望它是一个从图形中间开始垂直向下的的渐变色所以我们将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,表示这个颜色位于渐变的结束点。

  利用这样的方式我们就可以编码出一个类似于游戏中效果的颜色了。


3.Path和Border对与边界线的处理方式分析

  • Border 控件:

    Border 控件的边界线包含在 Border 的内部。 边界线的粗细由 BorderThickness 属性控制,它指定了边界线的宽度。BorderThickness 的默认单位是设备独立像素(Device Independent Pixels,DIPs)。 例如,如果将 BorderThickness 设置为 2,那么边界线的宽度将是2个DIPs。
     
  • Path 控件:
     

    Path 控件的边界线是基于 StrokeThickness 属性的中心位置来绘制的。 StrokeThickness 控制了边界线的粗细,但该属性表示边界线从中心点向两侧扩展的距离。 例如,如果将 StrokeThickness 设置为 2,那么边界线的实际宽度是4个DIPs,即两个DIPs在路径的内部,两个DIPs在路径的外部

  所以在这个有固定尺寸的图形中:我们将Border和path的thickness都设置成2, Margin都设置成 4 4 4 4,实际上展示出的效果 path的上边框高出了border。 因此,需要考虑到StrokeThickness来调整Path的Margin值。
  左边Margin已经设置为40,可以覆盖GreenLine,所以没有问题。顶部Margin应增加1px,设置为5px,右边和底部Margin无需改变。由于Path有固定的大小为118x28,所以只需要设置左边和顶部的Margin。 同时,由于顶部Margin增加了5px,底部可能看起来像被切掉了一半,就像这种情况。为了防止这种情况,可以将底部Margin设置为-5px。这是通过在底部移除顶部增加的5px来平衡整个布局的方法。另一种方法是保持底部Margin为0px。这两种方法都可以防止因顶部增加而导致底部被切断的情况。


4.利用Jamesnet.WPF Nuget制作动画

  在WPF中我们也能制作出很多非常生动的动画效果。这个项目中我们可以利用thickness animation为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时Paly文字移除Stop文字移动将来; UnChecked"时Stop文字移除Paly文字移动将来,这样接可以编码一个类似于上下翻动的动画效果了。

  为了能更加轻松的使用和编写动画效果,我们将WPF原有的动画效果进行汇总和整理,并且添加到了Jamesnet.WPF Nugetpackage中,简单的添加后就可以使用。 动画中我们可以通过Margin调整移动的距离;通过Duration设定动画的时间。
 


5.Clip属性
 


 

  由于Grid里都是相互重叠在一起的独立元素,所以在制作文字上下滚动的动画效果时,视觉上就会出现文字超出了边框的效果,为了解决这个问题,我们可以使用<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.Clip>是一个 XAML 元素,用于定义一个裁剪(Clipping)区域,它可以用来限制子元素的可见区域。裁剪区域通常是一个形状,例如矩形,只有位于裁剪区域内的内容部分会被显示,而超出裁剪区域的部分会被隐藏。 所以项目中我们将<Grid.Clip>的区域设定在path大小之内:Rect="0,5,165,28"
这样文字就只会在这个区域内显现,最终呈现出在path中上下滚动的效果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值