This article has been localized into Chinese by the community.
In this article series, we're building a complete Snake game from scratch. It makes sense to start with the Introduction and then work your way through the articles one by one, to get the full understanding.
If you want to get the complete source code for the game at once, to get started modifying and learning from it right now, consider downloading all our samples!
改善WPF贪吃蛇:让它看起来更像个游戏
在这系列文章的末尾期间,我们已经构建了一个在WPF中很酷的贪吃蛇游戏。我们已经实现了全部游戏-机制,结果是一个功能齐全的游戏。然而,这无疑要有一些改善,因为当前实现非常简单,因此,在下篇文章,我将会对我们的WPF贪吃蛇游戏做出一系列的改善,在这篇文章,我将会专注于让我们的游戏看起来更像个实际游戏!
正如现在看到那样,有着一个默认的窗体-样式/标题栏,我们的实现看起来不太像一个游戏,然而,我们过去需要标题栏去展示分数/速度信息,作为一个奖励,我们自动去获取默认的窗体按钮去最小化/最大化/关闭窗体:
此刻,我将想去完全移除默认Window标题栏,而是实现我们自己的顶部状态栏,来显示当前分数和速度,也实现一个专门的关闭按钮。所有这些都应该和当前游戏外观相匹配。对我们而言幸运的是,这是非常容易用WPF做到的。
添加一个自定义标题栏
第一步就是去在Window的声明中添加一些属性和一个新的事件。这现在看起来像这样:
<Window x:Class="WpfTutorialSamples.Games.SnakeWPFSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTutorialSamples.Games"
mc:Ignorable="d"
Title="SnakeWPF - Score: 0" SizeToContent="WidthAndHeight" ContentRendered="Window_ContentRendered" KeyUp="Window_KeyUp"
ResizeMode="NoResize" WindowStyle="None" Background="Black" MouseDown="Window_MouseDown">
所有的改变都在最后一行。我们设置了ResizeMode为NoResize和WindowStyle为None。这将会完全移除标题栏和其他围绕在Window的默认边框-这对于我们来说没啥问题,因为我们的游戏主要区域已经有5 像素的黑边框了。
你将也会注意到我已经订阅了一个新的事件-MouseDown事件。原因是因为我们丢失了默认的标题栏,这里不再有任何方法去给用户拖拽游戏从屏幕一个点到其他的点。对我们幸运的是,这非常简单去重建行为,例如靠我们自己,自定义的标题栏。然而,因为它看起来不像正常的标题栏,用户可能会拒绝了解如何去拖动,因此我决定简单的去让整个窗体表面可拖动。因此,在你的Code-behind中,默认的Window_MouseDown事件处理器看起来像这样:
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
有了这些,你的Window窗体无论你在哪里使用鼠标都能被拖动。下一步就是添加我们的自定义标题栏,去展示分数和速度,同时还有一个关闭按钮。Window XAML内部现在看起来像这样:
<DockPanel Background="Black">
<Grid DockPanel.Dock="Top" Name="pnlTitleBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontFamily" Value="Consolas" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="24" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Grid.Resources>
<WrapPanel Margin="10,0,0,0">
<TextBlock>Score:</TextBlock>
<TextBlock Name="tbStatusScore">0</TextBlock>
</WrapPanel>
<WrapPanel Grid.Column="1">
<TextBlock>Speed:</TextBlock>
<TextBlock Name="tbStatusSpeed">0</TextBlock>
</WrapPanel>
<Button Grid.Column="2" DockPanel.Dock="Right" Background="Transparent" Foreground="White" FontWeight="Bold" FontSize="20" BorderThickness="0" Name="btnClose" Click="BtnClose_Click" Padding="10,0">X</Button>
</Grid>
<Border BorderBrush="Black" BorderThickness="5">
<Canvas Name="GameArea" ClipToBounds="True" Width="400" Height="400">
</Canvas>
</Border>
</DockPanel>
然后不要忘记定义BtnClose_Click事件处理器:
private void BtnClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
我们过去实现了一个叫UpdateGameStatus()的方法,用来更新Window 标题属性-这个方法应该被改变用来使用新的TextBlock:
private void UpdateGameStatus()
{
this.tbStatusScore.Text = currentScore.ToString();
this.tbStatusSpeed.Text = gameTickTimer.Interval.TotalMilliseconds.ToString();
}
我将会告诉你所有关于我们刚刚做的,但是首先,让我们看看游戏现在的样子:
这看起来有点更酷了,对吧?但是让我们解释我们刚刚做了什么:如你所见,原始的带有GameArea Canvas的Border控件,然而现在却已经变成被一个DockPanel包裹着。这让我们更简单以Grid 面板的形式附加我们的新的标题栏到Window的顶部
Gird用了一系列酷的WPF手法,这些技术已经在本教程的其他地方讨论过了:我们使用ColumnDefinition去分割区域成两半等大的区域(为了分数和速度)。加上一个自动大小的第三列给到关闭按钮。你也将注意到我们使用WPF样式去应用同样的视觉效果到所有TextBlock 控件去,由于Gird中定义了给TextBlock控件的样式,所有这些控件都应用同样的自定义字体,字体大小,颜色和粗体。
也注意到多容易去自定义我们的Button控件用来关闭窗体,完全匹配上游戏的其他视觉和感觉,简单的通过使用标准属性-WPF是非常灵活的!
小结
在这篇文章中,我们已经让我们的WPF贪吃蛇实现看起来更像一个游戏,通过移除标准的Window外观和应用我们的自定义标题栏。在接下来的文章中,我们将会作出更多的改善!