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

构建 Windows Phone 7 拼图游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (88投票s)

2010年3月24日

CPOL

10分钟阅读

viewsIcon

239812

downloadIcon

6864

利用新的 Windows Phone 7 开发工具抢占先机。学习如何为 WP7 平台创建 Silverlight 版的 Sokoban 游戏。

title

我制作了一个游戏视频

请注意,在视频中,当我提到“点击”时,指的是在模拟器中使用鼠标,这相当于在真实设备上按下屏幕。

目录

引言

这篇文章对我来说有点离题,内容也会比我通常写的文章轻松一些。本文介绍的技术是上周才发布的,所以希望大家能谅解我没有对每个功能都进行深入探讨。我们将初步了解 Windows Phone 7,并使用 Silverlight 和 XNA 框架音频 API 的功能来创建一个益智游戏。

背景

上周在 MIX10 上,微软发布了新的 Windows Phone 7 开发工具。和许多开发者一样,我对此感到非常高兴。上周一在拉斯维加斯开始的 MIX10 开发者大会 2010,微软将重点放在了其 Microsoft Windows 7 手机及其开发工具上。我整个周末都在编写这个游戏,并尝试使用新的 WP7 功能。结论是?我真的很喜欢它。它是一个熟悉的环境,Silverlight 和 XNA,我相信它将对手机市场产生重大影响。和许多开发者一样,我曾有点嫉妒 iPhone 开发者,他们制作那些有趣的小应用。看起来他们能从像 iFart 这样荒谬的应用中赚大钱。我相信这很大程度上是炒作,如今的市场竞争非常激烈。但即便如此,我也想尝试一下那种场景,但遗憾的是,我既没有时间也没有兴趣去用 Objective-c 进入苹果的世界。是的,Novell 有它的 iPhone Touch,我也曾考虑过尝试一下。但即便如此,我认为我最终会感觉自己像在试图将一个方形块推入一个圆形孔。

WP7 登场。现在微软真是功不可没。是的,我知道我听起来像个粉丝,但我很高兴这些东西出现了。2008 年,我曾预测 Silverlight(第一个使用 .NET 的版本)1.1 版本会非常流行。好吧,那是个显而易见的预测,但现在很明显,Windows Phone 将会产生巨大影响。我们有熟悉的 Visual Studio 开发环境,一项构建灵活和动态界面的绝佳技术——Silverlight,以及最后是 应用商店。所有这些都是完美风暴的要素!

所以,为了尝试 WP7,我带回了我那个像 Mike Wazowski 的角色,再次制作 Alien Sokoban!几年前,我曾为另一篇文章中的 Silverlight 编写了大部分游戏逻辑。那时 Silverlight for .NET 还没有正式发布,它是 1.1 版本,我们甚至还没有内置的文本框;我们不得不自己编写。世事变迁,如今随着最新版本的 Silverlight,我们有了大量新功能。摄像头、VSM、COM 互操作、多播流,等等等等。(但遗憾的是,仍然没有内置的菜单控件!)。

Windows Phone 7 使用 Silverlight 3。开发工具包含一个模拟器,实际上它是一个虚拟机。这很棒,因为这就像拥有了手机,但又没有手机,这很有帮助,因为我没有手机。Visual Studio 中有完整的开发者集成;我们有调试器,可以在代码编辑器中将鼠标悬停在变量上,等等,就像在 Silverlight 中一样。

入门

如果您是 Silverlight 新手,我建议您先学习相关知识。这里有许多 CodeProject 上精彩的入门 Silverlight 文章,可以帮助您入门。

WP7 工具的安装

安装Windows Phone 开发工具 CTP,并且不要忘记阅读发行说明。请注意,如果您有 Visual Studio 2010 RC(请注意,您需要 RC 或更高版本,并且不能在同一台机器上安装 beta 版本),则该工具将集成到现有的 Visual Studio 安装中,否则将安装 Visual Studio 2010 Express。

install dev tools

图:安装 Windows Phone 7 开发工具 CTP

安装程序将自动下载并安装所需的组件。

如果您希望使用 Expression Blend 4 进行 WP7 开发,虽然本文不需要,但可以从 Christian Schormann 的博客下载并安装适用于 Windows Phone 的 Expression Blend 4 附加组件

安装完成后,请重启并启动 Visual Studio 2010。

new project

图:安装后,出现了新的 Window Phone 项目类型。

然后,您就可以开始工作了!

游戏 GUI

主用户界面由一个 PhoneApplicationPage 提供。这是创建新的 Windows Phone 应用程序时默认的宿主控件。

phone assemblies

图:安装开发者工具 CTP 后显示的 Windows Phone 程序集。

VS Screenshot

图:Visual Studio 设计环境。

开发者在 Visual Studio 2010 中处理 Windows Phone 应用时会感到非常自在。在这里,我们看到可以直接使用设计器,它提供了工作手机区域的可视化表示以供参考。

页面方向

在 Windows Phone 应用程序中托管应用程序最明显的影响之一是如何处理布局方向,特别是检测方向变化以及我们是否认为 UI 与特定方向兼容。PhoneApplicationPage 有一个可设置的属性名为 SupportedOrientations。这个 enum 值可以是 LandscapePortraitPortraitOrLandscape。通过在 XAML 或代码中为其赋值,我们可以限制应用程序的显示方式。对于 Alien Sokoban,我选择了 PortraitOrLandscape,因为我想让用户能够根据游戏网格的尺寸旋转它。需要注意的是,无法从用户代码设置方向,因为它被标记为SecurityCritical。当我们通过 Reflector 浏览到 Page.Orientation 属性时,可以看到这一点。

Page Orientation

图:Page.Orientation 属性是 SecurityCritical

SecurityCritical 是大多数 Silverlight 开发者熟悉的属性。这意味着如果我们尝试调用它,将会引发异常,因此它是不允许访问的。您可以在这里找到更多关于 Silverlight 代码安全模型的信息。

请注意,这是 Silverlight 安全,并非 Windows Phone 特有。这充分说明了 Windows Phone 使用的是完整版 Silverlight,而不是 Silverlight Lite。

从 Reflector 中,我们可以看到 Page.Orientation 不是依赖属性(也不会引发 PropertyChanged 事件),其其他属性也不是。因此,绑定到 PhoneApplicationPage 属性是行不通的。

方向可见性转换器

不经过一些额外的代码旁置工作,无法立即检测和更新页面元素。我想根据方向隐藏“Alien Sokoban”标题,因为它在纵向模式下太宽了。我可以简单地在 OrientationChanged 事件处理程序中设置 Visibility,但我选择在 MainPage 上创建了一个新的依赖属性,该属性在调用 OrientationChanged 事件处理程序时更新。为了转换方向值,我创建了一个 IValueConverter,并在 XAML 中使用它。当 Page.Orientation 属性发生变化时,标题 TextBlock 的 Visibility 的绑定也会随之改变。

<TextBlock Visibility="{Binding ElementName=Page, Path=PageOrientation, 
    Converter={StaticResource OrientationToVisibilityConverter}, 
				ConverterParameter= Landscape}" .../>

使用 ConverterParameter Landscape” 来指定我们希望仅在 Page.Orientation 属性是风景模式(包括 LandscapeLandscapeLeftLandscapeRight)时显示 TextBlock。以下是 OrientationToVisibilityConverter 的一个片段,展示了转换器如何将 PageOrientation 值转换为 Visibility 值。

public object Convert(object value, Type targetType, 
		object parameter, CultureInfo culture)
{
	var orientation = (PageOrientation)value;

	string showWhenOrientation = parameter.ToString().ToLower();
	bool show = false;
	switch (orientation)
	{
		case PageOrientation.Portrait:
		case PageOrientation.PortraitDown:
		case PageOrientation.PortraitUp:
			show = showWhenOrientation == "vertical";
			break;
		case PageOrientation.Landscape:
		case PageOrientation.LandscapeLeft:
		case PageOrientation.LandscapeRight:
			show = showWhenOrientation == "landscape";
			break;
	}

	return show ? Visibility.Visible : Visibility.Collapsed;
}

使用 XNA 框架音频 API

我非常惊喜地发现使用 XNA 框架播放音效非常容易。它适用于需要即时播放的短样本。但请注意,它对格式要求很严格。我发现只有 PCM 格式的 wav 文件受支持。我使用了GoldWave 将所有音频保存为 PCM 格式。对于较长的片段,使用更节省空间的格式(如 mp3)更有意义,但这需要使用 MediaElement 控件。

所有音效都在 MainPage.xaml.cs 代码旁边定义,如下面的摘录所示:

readonly SoundEffect footStepSoundEffect = 
	SoundEffect.FromStream(TitleContainer.OpenStream("Audio/Footstep.wav"));

然后,我们可以这样播放音效:

footStepSoundEffect.Play();

请注意,所有打算由 XNA 框架播放的音频文件的 Build Action 都必须设置为 Content。

Set as content

图:音频文件的 Build Action 必须设置为 Content。

过去在使用 Silverlight 中的 MediaPlayer 元素时,我注意到在排队媒体时可能会有短暂的延迟。使用 XNA 框架,播放是即时的;这是音效的关键要求。

主页面绑定

在实例化期间,MainPage 将一个 Game 实例分配给其 DataContext。它的大部分活动都由数据绑定和 Game 属性更改控制。MainPage 在此完整提供:

<phoneNavigation:PhoneApplicationPage 
    x:Class="DanielVaughan.Sokoban.UI.MainPage"
    x:Name="Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;
		assembly=Microsoft.Phone.Controls.Navigation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:controls="clr-namespace:DanielVaughan.Sokoban.UI" 
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
    SupportedOrientations="PortraitOrLandscape"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}" Orientation="Landscape">
    <phoneNavigation:PhoneApplicationPage.Resources>
        <controls:OrientationToVisibilityConverter 
		x:Key="OrientationToVisibilityConverter" />
        <Style x:Key="CenterLabels" TargetType="TextBlock">
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
        </Style>
        <Style x:Key="ToolBarWebdings" TargetType="Button">
            <Setter Property="Background">
                <Setter.Value>
                    <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                        <GradientStop Color="#FF9AFF95" Offset="0.21"/>
                        <GradientStop Color="#FF5DD757" Offset="0.589"/>
                        <GradientStop Color="#FF99FF93" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="FontFamily" Value="Webdings"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="Padding" Value="5 "/>
        </Style>
        <Style x:Key="OrdinaryButton" TargetType="Button">
            <Setter Property="Background">
                <Setter.Value>
                    <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                        <GradientStop Color="#FF9AFF95" Offset="0.21"/>
                        <GradientStop Color="#FF5DD757" Offset="0.589"/>
                        <GradientStop Color="#FF99FF93" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="Padding" Value="5 "/>
        </Style>
    </phoneNavigation:PhoneApplicationPage.Resources>

    <Grid x:Name="LayoutRoot" Background="White">        
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
               
        <controls:BackgroundControl Opacity=".3" />

        <Border VerticalAlignment="Top" Grid.Row="0" Height="60"  
                BorderBrush="#FFFFE63E" CornerRadius="10,10,10,10" 
		BorderThickness="2,2,2,2" Margin="0,0,0,0">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,-1.389" 
		StartPoint="0.5,2.389" SpreadMethod="Pad">
                    <GradientStop Color="#FFFF9900" Offset="1"/>
                    <GradientStop Color="#FFFF9900" Offset="0.58"/>
                    <GradientStop Color="#FFFFFFFF" Offset="0"/>
                </LinearGradientBrush>
            </Border.Background>
            <Grid>               
                <Rectangle Stroke="{x:Null}" Margin="5,3,5,18" 
                           RadiusX="10" RadiusY="10" Opacity="0.41">
                    <Rectangle.Fill>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                            <GradientStop Color="#FFECECEC" Offset="0"/>
                            <GradientStop Color="#FFFFFFFF" Offset="1"/>
                        </LinearGradientBrush>
                    </Rectangle.Fill>
                </Rectangle>
                <StackPanel Height="50" x:Name="stackPanel1" Margin="15,0,5,0" 
                            VerticalAlignment="Center" HorizontalAlignment="Stretch" 
                            Width="Auto"  Orientation="Horizontal">
                    <TextBlock VerticalAlignment="Center" 
			Foreground="White" Text="Code:" TextWrapping="Wrap"/>
                    
                    <TextBox Text="{Binding Path=LevelCode, Mode=OneWay}"
                             x:Name="textBox_LevelCode" MaxLength="5"
                             Opacity="0.4" Width="110" TextAlignment="Center"
                             VerticalAlignment="Center" 
                             HorizontalContentAlignment="Center" 
                             GotFocus="TextBox_LevelCode_GotFocus" 
                             LostFocus="TextBox_LevelCode_LostFocus" 
                             KeyUp="TextBox_LevelCode_KeyUp"
                             Background="White" />

                    <Button Style="{StaticResource ToolBarWebdings}" 
			Margin="0,-10,0,0" Height="10" Content=""
                            Click="Button_Undo_Click"/>
                    <Button Style="{StaticResource ToolBarWebdings}" 
			Margin="0,-10,0,0" Height="10" Content=""
                            Click="Button_Redo_Click"/>
                    <TextBlock Visibility="{Binding ElementName=Page, 
			Path=PageOrientation, 
                            Converter={StaticResource OrientationToVisibilityConverter}, 
			ConverterParameter=Landscape}"
                        VerticalAlignment="Center" HorizontalAlignment="Center" 
                               TextAlignment="Center" Foreground="White" 
				Text="Alien Sokoban" 
                               FontFamily="Tahoma" FontSize="36" Margin="45,0,0,0"/>
                </StackPanel>
                <Grid HorizontalAlignment="Right">
                    <StackPanel Orientation="Horizontal">
                        <StackPanel VerticalAlignment="Center">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Style="{StaticResource CenterLabels}" 
				Text="Level "/>
                                <TextBlock x:Name="label_LevelNumber" 
                                           Style="{StaticResource CenterLabels}" 
                                           Text="{Binding Path=Level.LevelNumber}"/>
                                <TextBlock Style=
				"{StaticResource CenterLabels}" Text="/"/>
                                <TextBlock Style="{StaticResource CenterLabels}" 
				Text="{Binding Path=LevelCount}"/>
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Style="{StaticResource CenterLabels}" 
				Text="Moves "/>
                                <TextBlock x:Name="label_Moves" 
				Style="{StaticResource CenterLabels}" 
                                           Text="{Binding Path=Level.Actor.MoveCount}"/>
                            </StackPanel>
                        </StackPanel>
                        <Button Style="{StaticResource ToolBarWebdings}" 
				Margin="0,-8,0,0" 
                                Height="10" x:Name="button_RestartLevel" Width="80" 
                                Click="Button_RestartLevel_Click" 
                                IsTabStop="False"  Content=""
                                HorizontalAlignment="Right" >
                            <ToolTipService.ToolTip>
                                <ToolTip Content="Restart"></ToolTip>
                            </ToolTipService.ToolTip>
                        </Button>
                    </StackPanel>

                </Grid>
            </Grid>
        </Border>
        <!-- The Game grid. -->
        <Border Grid.Row="1" Padding="5" BorderBrush="#919292" CornerRadius="12" 
                BorderThickness="0" Background="Transparent">
            <Grid x:Name="grid_Game" />
        </Border>

        <Grid x:Name="textBlock_PressAnyKey" 
              Background="#006DCAC1"
              HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.RowSpan="2">
            <StackPanel VerticalAlignment="Center" HorizontalAlignment="Stretch" 
                        Background="#556DCAC1">
                <TextBlock x:Name="feedbackControl" 
                           VerticalAlignment="Center"
                           HorizontalAlignment="Center" Text="TextBlock" FontSize="32"/>
                <Button Style="{StaticResource OrdinaryButton}" Content="Continue" 
                        VerticalAlignment="Center" HorizontalAlignment="Center"
                        Click="Button_Continue_Click"/>     
            </StackPanel>
        </Grid>
    </Grid>
    
</phoneNavigation:PhoneApplicationPage>

游戏网格中的每个单元格都用一个 CellControl 填充。这发生在 MainPageInitializeLevel 方法中。

void InitialiseLevel()
{
	cellControls.Clear();
	commandManager.Clear();

	grid_Game.Children.Clear();
	grid_Game.RowDefinitions.Clear();
	grid_Game.ColumnDefinitions.Clear();

	for (int i = 0; i < Game.Level.RowCount; i++)
	{
		grid_Game.RowDefinitions.Add(new RowDefinition());
	}

	for (int i = 0; i < Game.Level.ColumnCount; i++)
	{
		grid_Game.ColumnDefinitions.Add(new ColumnDefinition());
	}

	var cellSize = CalculateCellSize();
	
	for (int row = 0; row < Game.Level.RowCount; row++)
	{
		for (int column = 0; column < Game.Level.ColumnCount; column++)
		{
			Cell cell = Game.Level[row, column];
			cell.PropertyChanged += cell_PropertyChanged;

			CellControl cellControl = new CellControl(cell);
			cellControl.MaxHeight = cellControl.MaxWidth = cellSize;
			cellControl.Click += Cell_Click;

			Grid.SetColumn(cellControl, column);
			Grid.SetRow(cellControl, row);
			grid_Game.Children.Add(cellControl);
			cellControls.Add(cellControl);
		}
	}

	/* Play the intro audio clip. */
	PlayAudioClip(introSoundEffect);
	/* Listen for actor property changes. */
	//Game.Level.Actor.PropertyChanged += Actor_PropertyChanged;

	RefreshGameGrid();
}

通常,我不建议将这种 UI 逻辑代码放在代码旁边,因为我更喜欢使用 MVVM 方法。但是,为了方便起见,我采用了这种方法。

CellControl 被分配了一个 Game 单元格,并根据单元格的状态改变其外观。如果被认为是墙,它显示一个灰色方块,等等。

游戏玩法

请参阅游戏视频以了解游戏玩法概览。游戏逻辑也已在此此处进行了文档记录。

手机屏幕键盘

Windows Phone 7 允许开发人员指定用户可以通过屏幕键盘输入的数据类型。它也是上下文感知的,并且当 TextBox 获得焦点时会放大到 TextBox。要更改键盘以使其更适合正在输入的数据,可以使用 InputScope

enter level code

图:使用屏幕显示输入关卡代码

例如,要指定对默认屏幕键盘(如上所示)的修改,请将 InputScope 元素应用于 TextBox

<TextBox> 
  <TextBox.InputScope> 
    <InputScope> 
      <InputScope.Names> 
        <InputScopeName NameValue="EmailNameOrAddress"/> 
      </InputScope.Names> 
    </InputScope> 
  </TextBox.InputScope> 
</TextBox>

通过使用 EmailNameOrAddress NameValue ,显示的键盘包含 QWERTY 字符以及 .com 和 @ 符号。

以下是有效输入范围值列表:

SIP 布局 XAML 或枚举值 SIP 描述
默认值 默认和其他标准输入范围值 标准 QWERTY 键盘
文本 文本 具有自动更正和文本建议等功能的标准文本
Web Url 用户键入 URL
电子邮件地址 EmailSmtpAddress 用户键入电子邮件地址
电子邮件名称或地址 EmailNameOrAddress 用户键入电子邮件名称或地址
映射 映射 用户键入要在地图上搜索的位置
电话号码 TelephoneNumber 用户键入电话号码
搜索 搜索 用户键入搜索查询
SMS 联系人 NameOrPhoneNumber 用户在 SMS 收件人字段中键入
聊天 聊天 使用缩写等智能功能的文本输入

来源:Don's Expression Blend Blog

结论

在本文中,我们学习了如何使用 Silverlight 3 创建 Windows Phone 益智游戏。我们研究了 PageOrientation 及其在控制布局中的应用。我们检查了屏幕键盘,以及如何根据上下文选择不同的键盘,还简要介绍了 XNA 框架音频 API 及其在播放音效中的用途。

我对这个平台的未来感到非常兴奋。在短暂接触之后,我已经感觉很适应了。希望您觉得这个项目有用。如果觉得有用,请帮忙给它评分和/或在下方留下反馈。这将帮助我改进下一篇文章。

历史

  • 2010 年 3 月
    • 出版日期
  • 2010 年 7 月
    • 更新为 Windows Phone 7 Beta
© . All rights reserved.