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

Tap Madness,适用于超级本的 Windows 8 Metro 游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (4投票s)

2012年10月18日

CPOL

17分钟阅读

viewsIcon

15510

开发一款利用下一代超级本硬件的有趣游戏,同时学习 Windows 8 Metro 开发

引言

在本文中,您将有机会跟随我从零开始制作 Metro 游戏。我选择游戏最重要的原因是可以展示超级本独特的硬件功能。这意味着,在本文中,我们将有机会讨论不同类型的触摸、检测摇晃等手势、使用陀螺仪以及实现各种疯狂的功能,让我们的游戏成为一种有趣的体验。

正如您从我的记录中看到的那样——我喜欢撰写详细的指南文章,希望能让读者在阅读后对如何进行下一步工作有一个清晰的思路。由于 Windows 8 开发是一个相对较新的话题,我预计许多读者都对快速入门自己的开发感兴趣。这就是我将本文组织成一种 Windows 8 开发快速入门介绍的最大原因。这意味着我不仅会介绍如何使用超级本独特的硬件功能,还会在整篇文章中尽量提供更通用的技巧——那些帮助我开始开发并希望也能为您节省时间的技巧。

请注意,本文仍在撰写中。再加上它旨在成为一份全面的指南,这意味着非常欢迎反馈。如果您对如何更好地完成我在文章中提到的某件事有想法,请在评论中发布,我将尽力将您的建议纳入文章中并适当地引用您。

文章将分为三章,即:

  1. 基础 - 如何设置 IDE 并避免常见的安装问题,Metro 开发有哪些选项,以及启动 Metro 开发所需的基础知识。
  2. 编码启动 - 如何在 Metro 中执行常见任务(如模态弹出窗口)、保存用户设置、架构应用程序、处理样式等。
  3. 深入探讨 - 深入讨论 UIElement 和动画、数据绑定、触摸手势的类型以及如何在代码中处理它们、处理通过网络发送的数据。

但在我们开始之前,让我们快速看一眼疯狂点击中的游戏画面。

Game in Action

现在我们对结果有了概念,我们来尝试构建它,好吗?

基础

对于那些拥有 Windows 8 和 Visual Studio 2012 并已经体验过许多 Metro “Hello World” 应用程序的用户,一个快速建议是:请随意直接跳到编码部分

其他人,让我们快速回顾一下 Windows 8 和 Visual Studio 2012 的设置。

安装 Windows 8 和 Visual Studio 2012

  1. 我衷心建议您将 Windows 8 安装在虚拟机中,以便您可以将其与您的日常 Windows 并行运行。我的指南假定您将使用 VirtualBox——因为它是一款优秀的虚拟机,最重要的是,无论您使用哪个版本的 Windows,它都能正常工作。

    如果您拥有 Windows 7 / Vista 专业版,您可以选择 Virtual PC
  2. 下载并安装 VirtualBox 后,请务必在为 Windows 8 安装创建的虚拟机中设置以下参数;否则,您可能会遇到奇怪的问题(VisualStudio 或您的应用程序可能会无故崩溃)
    • 通用 -> 高级
      • 共享剪贴板 -> 双向(我们需要安装 Guest Additions 才能启用剪贴板共享,步骤 5)
    • 系统 -> 主板
      • 芯片组:ICH9
      • 勾选启用 IO APIC
      • 基本内存 >= 2048
    • 系统 -> 处理器 >= 2
    • 显示
      • 取消勾选 3D 加速
      • 取消勾选 2D 视频加速
    • 网络 -> 适配器 1
      • 勾选启用网络适配器
      • 连接到:桥接网卡
      • 名称:(从列表中选择您的局域网网卡)
  3. 下载 Windows 8 发布预览版 ISO
  4. 将下载的 ISO 镜像附加到您在步骤 2 中创建的虚拟机并安装 Windows 8
  5. Windows 8 安装完成后,安装 Virtual Box 的增强功能(设备 -> 安装增强功能)
    • a. 如果您在此步骤需要帮助,请查看以下指南 http://www.sysprobs.com/guide-install-windows-8-virtualbox
  6. 6. 确保不要陷入安装 Microsoft Visual Studio 2012 Express 的繁琐过程,因为它在大多数情况下不适用于 Windows 8 RC。相反,请下载 90 天 Ultimate 评估版(这是 直接链接
  7. 就是这样!完成后启动 Visual Studio!

我第一次安装 Windows 8 时帮助我导航的一些零散技巧

  • 最快的桌面方式仍然是 Windows键+D
  • 如果缺少“我的电脑”(及其他图标):右键单击桌面 -> 个性化 -> 更改桌面图标 -> 勾选您想要的图标。
  • 显示文件扩展名:打开“我的电脑” -> 视图(顶部栏) -> 勾选“文件扩展名”。

创建你的第一个项目

当你启动 Visual Studio 时,你可能会想知道当你选择:文件 -> 新建 -> 项目 时应该选择哪个项目。

基本上,Windows 8 将有两种类型的应用程序:“经典”和“Metro”。你问有什么区别?

  1. 经典 - 应用程序是标准的“旧”桌面 Windows 应用程序。这意味着它们的观感将与 Windows 7 及更早版本的 Windows 上的应用程序相同。这些应用程序的发布没有限制,你可以为超级本竞赛(有 6 个类别)制作其中一个。
  2. Metro – 专为 Windows 8 设计的新一代应用程序。预计这些应用程序将 exclusively 通过 Microsoft 的 AppStore 分发。它们需要遵循大量 Microsoft 指南,并与“Windows 8 体验”完全集成。超级本竞赛为此类应用程序设有一个类别。

在本文中,我将构建一个 Metro 应用程序——既然我们在学习新技巧,那就不妨全力以赴,对吧?

你有选择

请注意,您可以使用不同的风格构建 Windows Metro 应用程序

  • JS & HTML5
  • C# & XAML
  • VB.NET & XAML
  • C++ & DirectX

为简明起见,我不会在此主题上深入探讨——如果你们希望看到 JS & HTML5 版本的指南,我相信你们会在评论中告诉我 ;)

所以,对我们来说,目前是:文件 -> 新建 -> 项目 -> Visual C# -> Windows Metro 样式 -> 空白应用程序

编码启动

在我们深入代码之前,让我们先了解一些我们需要掌握的重要概念。

我们需要理解的第一件事是 Windows Metro 应用程序的生命周期管理。与“标准 Windows 7 桌面 PC”不同,超级本等个人电脑的功能将与现在的智能手机类似——用户可能经常发起挂起电源状态。应用程序应注意到这一点,并在收到挂起通知时保存其状态。

此外,传统的“关闭应用程序”模式也发生了一些变化。事实上,如果你想开怀大笑,可以在 Google 上搜索“如何关闭 Windows 8 Metro 应用程序”。大多数 Windows 8 应用程序没有“退出”按钮(正如 Windows Metro 应用程序指南所建议的那样),由于大多数人不知道 ALT+F4,你可以找到一些相当疯狂的“关闭教程”。但请记住,即使你使用 ALT+F4,应用程序也不会立即关闭——你的应用程序会先被挂起(并收到通知),然后在 10 秒后终止。

您应该知道,只有当您的应用程序从运行状态变为挂起状态时,操作系统才会通知您。当您的应用程序终止时,您不会收到通知!这很重要,因为挂起的应用程序可能随时被终止——如果操作系统需要为其他应用程序释放内存或节省电量。因此,如果用户通过 ALT-TAB 切换出您的应用程序,并不能保证它会在后台无限期地保持挂起状态。

Game in Action

从上一段中最重要的启示是,每当您收到挂起通知时,都应该保存任何关键数据。幸运的是,微软简化了此项功能的实现,只需点击几下即可生成您所需的一切。让我们看看它是如何工作的。

  1. 右键单击项目
  2. 添加 -> 新建项
  3. 选择 Windows Metro 样式 -> 基本页面
  4. 将文件名命名为 TitlePage.xaml,然后按“确定”。
  5. 系统将提示您“自动添加缺失文件”。选择“是”。

现在,如果您查看 Common 目录,您会发现,除了在根目录中添加 TitlePage.xaml 之外,Visual Studio 还生成了许多文件(之前那里只有 StandardStyles.xaml)。对于我们目前的话题,最重要的类是 SuspensionManager 和 LayoutAwarePage。

与传统应用程序不同,Metro 应用程序更像网站。您没有表单,但有页面。在 99% 的情况下,您不会同时打开多个表单——通常只会有一个页面占据全屏。

考虑到我们之前讨论的应用程序生命周期管理——无论您的应用程序是被挂起然后恢复(在这种情况下状态将自动恢复),还是被终止然后被激活(在这种情况下您需要手动恢复状态),当用户通过 ALT-TAB 切换回您的应用程序(或再次启动它)时,他期望看到应用程序处于他离开时的状态。

SuspensionManager 类通过简化状态保存过程提供帮助。首先,如果您调用 SuspensionManager.RegisterFrame(root, "appFrame");,您在 LayoutAwarePage 之间导航的历史记录将自动保存。然后,您只需在应用程序重新启动后调用 SuspensionManager.RestoreAsync 即可直接跳回“正确”的页面。其次,LayoutAwarePage 类提供 SaveState 和 LoadState 事件,这些事件会在页面状态需要保存或加载时自动调用。

现在我们知道如何处理不同页面之间的导航,接下来看看我们需要哪些页面并着手实现它们。

标题页(主菜单)

在我们的主菜单上,我们希望为用户提供几个选项,每个选项都将帮助我们解决和说明不同的开发挑战。

  1. 开始游戏 – 我们开发工作的核心,我们将专门用一章来讲解这个页面。
  2. 高分 – 处理远程数据,保存用户分数
  3. 关于 – 允许我们讨论 TextBlocks、将文本链接到网站以及样式
  4. 退出 – 只是一个有趣的按钮 – 这样我们的用户就不必搜索“终止 Windows 8 应用程序指南”

既然我们只需要 4 个按钮,我们的 XAML 就不会太复杂。

<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="30" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition MinWidth="200" />
    </Grid.ColumnDefinitions>

    <Button x:Name="btnStartGame" Grid.Row="0" Content="{StaticResource StartGame}" Click="btnStartGame_Click_1" Style="{StaticResource MainMenuButtonStyle}" />
    <Button x:Name="btnHighScores" Grid.Row="1" Content="{StaticResource HighScores}" Click="btnHighScores_Click_1" Style="{StaticResource MainMenuButtonStyle}" />
    <Button x:Name="btnAbout" Grid.Row="2" Content="{StaticResource About}" Click="btnAbout_Click_1" Style="{StaticResource MainMenuButtonStyle}" />
    <TextBlock Grid.Row="3" />
    <Button x:Name="btnExit" Grid.Row="4" Content="{StaticResource Exit}" Click="btnExit_Click_1" Style="{StaticResource MainMenuButtonStyle}" />
</Grid>

有几件事值得进一步解释

  1. 实现“只是为了好玩”的退出按钮并不难。有趣的是,尽管指南建议 Metro 应用程序不带退出选项,但关闭应用程序却非常简单——在 btnExit_Click_1 事件处理方法中,我们只需调用:App.Current.Exit();
  2. StaticResources 可以引用您内联定义或在外部文件中定义的资源。在我们的示例中,为了演示目的,我内联定义字符串,并在外部 Styles/CustomStyle.xaml 文件中定义样式。
  3. 所以,字符串是在 `` 内部内联定义的,像这样
    <Page.Resources>
        <x:String x:Key="AppName">Tap Madness</x:String>
    
        <x:String x:Key="StartGame">Start Game</x:String>
        <x:String x:Key="HighScores">High Scores</x:String>
        <x:String x:Key="About">About</x:String>
        <x:String x:Key="Exit">Exit</x:String>
    </Page.Resources>        
  4. 请注意,您需要在 App.xaml 中通过添加条目。完成后,您可以用不同的样式填充该文件。
    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:TapMadness">
           
        <Style x:Key="MainMenuButtonStyle" TargetType="Button">
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="FontSize" Value="40" />
        </Style>        
    
    </ResourceDictionary>
    
  5. 无需手动为每个 UIElement 添加 Style 属性(Style="{StaticResource MainMenuButtonStyle}")即可为控件设置样式。我们将在 MainPage 中这样做,为所有 TextBlock 设置样式,所以请留意。

对于高分,我们显然需要用户的名字。这可以通过多种方式完成,但为了研究弹出窗口和设置,让我们遵循这条路径:当应用程序首次启动时,我们将制作一个模态对话框,并要求用户输入其名字。模态对话框应该不会太难,对吧?

嗯,正如您所发现的——真正的模态对话框目前并不是 .NET Framework 的一部分。MessageBox(现在称为 MessageDialog)在那里。

var v = new Windows.UI.Popups.MessageDialog("Content", "Title");
v.ShowAsync();

但遗憾的是,您只能将字符串传递给此类别;您不能使用自定义的 UserControl(例如)。

如果您正在使用 JS/HTML5,您可以使用漂亮的 WinJS.UI.Flyout,但对于所有其他技术,您需要利用基类 Popup 并自行完成繁琐的工作。

在搜索能让我调用类似 Flyout 的模态对话框的库无果后,我被迫自己编写了一个类。这个类允许您包装您的用户控件并以美观的方式显示它。

public class PopupBase : UserControl
{
    public Popup ShowPopup(FrameworkElement source)
    {
        if (ParentPopup == null)
        {
            // prepare popup and grid
            Popup flyout = new Popup();
            Grid rootGrid = new Grid();

            rootGrid.RowDefinitions.Add(new RowDefinition());
            rootGrid.RowDefinitions.Add(new RowDefinition());
            rootGrid.RowDefinitions.Add(new RowDefinition());

            addGrid(rootGrid, Color.FromArgb(150, 0, 0, 0), 0);
            addGrid(rootGrid, Color.FromArgb(150, 0, 0, 0), 2);
            Grid cont = addGrid(rootGrid, Color.FromArgb(255, 19, 135, 67), 1);

            cont.Children.Add(this);

            var windowBounds = Window.Current.Bounds;

            rootGrid.Width = windowBounds.Width;
            rootGrid.Height = windowBounds.Height;

            flyout.IsLightDismissEnabled = false;

            flyout.Child = rootGrid;

            // spread control
            this.HorizontalAlignment = HorizontalAlignment.Center;
            this.VerticalAlignment = VerticalAlignment.Center;

            //
            ParentPopup = flyout;
        }

        ParentPopup.IsOpen = true;
        return ParentPopup;
    }

    private static Grid addGrid(Grid rootGrid, Color background, int at)
    {
        Grid dark1 = new Grid();
        dark1.Background = new SolidColorBrush(background);
        Grid.SetRow(dark1, at);
        rootGrid.Children.Add(dark1);

        return dark1;
    }

    public Popup ParentPopup { get; set; }
    public void ClosePopup()
    {
        ParentPopup.IsOpen = false;
    }
}

要使用这个类,只需创建一个 XAML UserControl 并使其继承自 PopupBase。最后,当您需要弹出窗口时,只需实例化您的 UserControl 并调用 ShowPopup,然后传入您的 pageRoot。在我们的游戏中,我们希望确保在首次启动时显示弹出窗口,此时我们还没有用户的名字。所以我们这样做:

private ProfileUserControl _pu = new ProfileUserControl();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    if (GameState.Instance.PlayerName == null)
    {
        _pu.ShowPopup(pageRoot);
    }
}

结果如下

Popup

现在模态对话框已就绪,让我们看看应用程序设置的加载和保存。PlayerName 属性的实现如下:

// GameState class
public string PlayerName
{
    get
    {
        if (_playerName == null)
            _playerName = UtilFunctions.LoadRoamingSetting
("userName");
        return _playerName;
    }
    set
    {
        _playerName = value;
        UtilFunctions.SaveRoamingSetting("userName", _playerName);
    }
}

// ...

// UtilFunctions class
public static T LoadRoamingSetting(string key)
{
    Windows.Storage.ApplicationDataContainer roamingSettings =
        Windows.Storage.ApplicationData.Current.RoamingSettings;
    if (roamingSettings.Values.ContainsKey(key))
    {
        return (T)roamingSettings.Values[key];
    }
    else
    {
        return default(T);
    }
}
public static void SaveRoamingSetting(string key, object val)
{
    Windows.Storage.ApplicationDataContainer roamingSettings =
        Windows.Storage.ApplicationData.Current.RoamingSettings;
    roamingSettings.Values[key] = val;
}

重要的是要说明应用程序设置有两种:漫游设置本地设置。当您保存的设置特定于用户当前使用的机器时,您会使用本地设置。另一方面,当您希望设置在不同机器之间传播时,您会使用漫游设置——此类会自动在后台将它们上传到云端。这意味着,如果我们的用户使用相同的 Windows 凭据登录另一台机器并启动游戏,他将无需两次设置游戏内名称。

关于页面

-即将推出

高分页面

-即将推出

深入

现在我们已经搭建了游戏的基础,是时候深入主要部分——编码引擎了。

引擎的首要任务是生成和动画化我们将通过触摸、加速计以及 Windows 8 超级本附带的其他炫酷硬件进行交互的形状。那么如何实现呢?

让形状跳舞

动画形状实际上比初看起来要困难。让我们看看我们能想到的最简单的动画代码。

// generate shape
Shape rect = new Rectangle();
rect.Width = 10;
rect.Height = 10;
rect.Fill = new SolidColorBrush(Colors.Blue);
// add to canvas
Canvas.SetLeft(rect, 300);
Canvas.SetTop(rect, 300);
mainFrame.Children.Add(rect);

// generate animation
DoubleAnimation da = new DoubleAnimation();
da.From = 10;
da.To = 100;
da.Duration = new Duration(TimeSpan.FromSeconds(5));
da.AutoReverse = true;
da.EnableDependentAnimation = true;  // animation won’t work without this 


// generate storyboard
Storyboard sb = new Storyboard();

Storyboard.SetTarget(da, rect);
Storyboard.SetTargetProperty(da, "Width");
// Storyboard.SetTargetProperty(da, "Height"); //Only one property unfortunately :(

sb.Children.Add(da);

sb.Begin();

查看代码,第一个也是最明显的问题是,我们不能对形状的 Width 和 Height 都使用一个 DoubleAnimation——我们需要创建两个实例。这并不是什么大问题,如果到此为止的话。但是因为我们计划使用 Canvas.SetLeft 和 Canvas.SetTop——并且没有定义形状的中心点,而是定义了顶部和左侧位置,所以我们还需要修改这两个属性。

显然,解决这个问题有很多方法,但为了保持简单,我们选择为每个形状实现一个“动画”属性。我们将尝试通过使用 EnableDependentAnimation 来以“不那么令人兴奋”的方式实现它;如果您想了解动画属性的任务有多复杂,请阅读此页面上的文章

这是我们用于保存形状的类的基础

public class FunkyShape : UserControl
{
    public double Animator
    {
        get { return (double)GetValue(AnimatorProperty); }
        set { SetValue(AnimatorProperty, value); }
    }

    public static DependencyProperty AnimatorProperty;
    static FunkyShape()
    {
        AnimatorProperty = DependencyProperty.Register("Animator", typeof(double), typeof(FunkyShape),
        new PropertyMetadata(DependencyProperty.UnsetValue, new PropertyChangedCallback(Animator_Changed)));
    }

    private static void Animator_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        double delta = (double)e.NewValue - (double)e.OldValue;

        ((FunkyShape)d).ProcessDelta((double)e.NewValue, delta);
    }

    private void ProcessDelta(double val, double delta)
    {
        Holder.Width = val;
        Holder.Height = val;

        // Keep shape centered
        HolderPosition.X -= delta / 2;
        HolderPosition.Y -= delta / 2;
            
        // Keep shape centered
        HolderRotation.CenterX = HolderPosition.X;
        HolderRotation.CenterY = HolderPosition.Y;       
    }

    private Shape Holder;

    // Rest of the class...
}

请注意,我们需要将 Animator 注册为 DependencyProperty。这反过来意味着我们被迫通过 Animator_Changed 方法处理更改。在该方法中,我们不仅增加/减小了形状的大小,还确保它保持在原位。

现在,让我们看看 FunkyShape 类中处理形状创建并将其添加到 Canvas 的代码。

public static FunkyShape GenerateRandom(Canvas canvas, Action
<FunkyShape> tapped, Action<FunkyShape> expired)
{
    Shape s = null;
    int shapeType = UtilFunctions.Rand.Next(3);

    if (shapeType == 0)
    {
        s = new Ellipse();
    }
    else if (shapeType == 1)
    {
        s = new Rectangle();
    }
    else if (shapeType == 2)
    {
        s = new Triangle();
    }

    Color c = new Color()
    {
        A = byte.MaxValue,
        R = (byte)UtilFunctions.Rand.Next(0, 256),
        G = (byte)UtilFunctions.Rand.Next(0, 256),
        B = (byte)UtilFunctions.Rand.Next(0, 256)
    };
    s.Fill = new SolidColorBrush(c);

    int margin = 10;
    Point p = new Point()
    {
        X = UtilFunctions.Rand.Next(margin, (int)Window.Current.Bounds.Width - margin),
        Y = UtilFunctions.Rand.Next(margin, (int)Window.Current.Bounds.Height - margin)
    };

    return new FunkyShape(canvas, s, p, tapped, expired, 200, 6000);
}

private Action<FunkyShape> _tapped;
private Action<FunkyShape> _expired;
public FunkyShape(Canvas playground, Shape shapeToInit, Point position, Action<FunkyShape> tapped, Action<FunkyShape> expired, int growToSize, int halfTimeTap)
{
    Holder = shapeToInit;

    TransformGroup myTransformGroup = new TransformGroup();
    var tt = new TranslateTransform()
    {
        X = position.X,
        Y = position.Y
    };
    myTransformGroup.Children.Add(tt);
    RotateTransform rt = new RotateTransform()
    {
        CenterX = tt.X,
        CenterY = tt.Y,
        Angle = UtilFunctions.Rand.Next(360)
    };
    myTransformGroup.Children.Add(rt);

    Holder.RenderTransform = myTransformGroup;
    Holder.RenderTransformOrigin = new Point(0.5, 0.5);
    Holder.Tapped += Holder_Tapped;

    // init done
    playground.Children.Add(Holder);

    //
    g1 = GrowAnimation(growToSize, halfTimeTap);

    //
    _tapped = tapped;
    _expired = expired;
}

Storyboard sb = new Storyboard();
DoubleAnimation g1;
void Holder_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    sb.Stop();

    Holder.Width = 0;
    Holder.Height = 0;

    e.Handled = true;
    _tapped(this);
}

public void Animate()
{   
    Storyboard.SetTarget(g1, this);
    Storyboard.SetTargetProperty(g1, "Animator");
    sb.Children.Add(g1);
    sb.Completed += sb_Completed;
    sb.Begin();
}

void sb_Completed(object sender, object e)
{
    _expired(this);
}

public DoubleAnimation GrowAnimation(int growToSize, int halfTapTime)
{
    DoubleAnimation growAnimation = new DoubleAnimation();
    growAnimation.Duration = TimeSpan.FromMilliseconds(halfTapTime);
    growAnimation.From = 0;
    growAnimation.To = growToSize;
    growAnimation.AutoReverse = true;
    growAnimation.EnableDependentAnimation = true;
    return growAnimation;
}

所有这些代码都只是为了让我们可以随时轻松地将形状添加到游戏画布中。

private void addShape()
{
    FunkyShape fs = FunkyShape.GenerateRandom(mainFrame, ShapeTapped, ShapeExpired);
    fs.Animate();
}

private void ShapeExpired(FunkyShape fs)
{
    Logic.GameState.Instance.ExpiredShape(fs);
    mainFrame.Children.Remove(fs);
}

private void ShapeTapped(FunkyShape fs)
{
    Logic.GameState.Instance.ScoreShape(fs);
    mainFrame.Children.Remove(fs);
}

您可能会认为这就是全部了——我们现在应该能够在 1 秒计时器内实现 addShape 的调用,通过按 F5 运行我们的解决方案,然后看到形状创建的盛宴在屏幕上闪耀。

但如果您尝试这样做,您会遇到以下异常:
WinRT 信息:无法解析指定对象上的 TargetProperty Animator。

所以,我们实际上还需要做一件简单的事情,而我花了整整一天的时间反复尝试和在网上搜索才找到。你需要在 XAML 中 Canvas 所在的位置添加一个 FunkyShape 的实例(这就是我们让 FunkyShape 类继承自 UserControl 的原因)。

<Canvas Name="mainFrame" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
	<Tm:FunkyShape />
</Canvas>

我不确定为什么会有这个要求,因为 MSDN 在这方面没有太多说明。此外,如果您查阅 MSDN 上关于动画的示例,您会发现它们大多使用 StoryBoards 和在 XAML 中定义的 Animation 实例,而代码后置方法进行动画的示例则很少。因此,如果任何人能对此主题提供更多见解,请随意在评论中发表。

用不同的方式触摸我

查看形状生成代码,您可以看到我们已经处理了形状上的点击。更重要的是——当形状被点击时,我们计算点击得分。如果形状在恢复到大小 0 之前未被点击,我们则计入玩家的失分。

但是,如果我们想通过要求用户在形状是圆形时点击,在形状是正方形时滑动,或者在形状是三角形时捏合来提供更丰富的体验呢?

幸运的是,.NET Framework 为一些手势提供了高级事件。我们已经看到了 Tapped,但 DoubleTapped、RightTapped 和 Holding 也可用。您可以通过更改正确的属性(IsTapEnabled、IsDoubleTapEnabled、IsRightTapEnabled、IsHoldingEnabled)来禁用这些事件的任何处理。

至于更复杂的触摸手势,如滑动、捏合或旋转——您需要手动处理它们。您能够实现哪些手势取决于您想要支持的输入方案。Microsoft 根据您想要在应用程序中实现的触摸支持量为您提供了两组事件:

  1. 指针事件——这些事件适用于您希望同时支持鼠标和触摸输入的情况。此组中有 5 个事件,名称都非常直观。
    • PointerPressed (指针按下)
    • PointerMoved (指针移动)
    • PointerReleased (指针释放)
    • PointerExited (指针退出)
    • PointerEntered (指针进入)
    通过这些事件,您可以处理简单的触摸手势,如滑动。
  2. 操作事件 - 当您希望在应用程序中提供高级、多指交互时,您需要利用这些事件。与上一组类似,我们也有 5 个事件:
    • ManipulationStarting (操作开始)
    • ManipulationStarted (操作已开始)
    • ManipulationDelta (操作增量)
    • ManipulationInertiaStarting (操作惯性开始)
    • ManipulationCompleted (操作完成)
    对于这些事件,您需要一台带有触摸屏的电脑。或者,您可以使用模拟器进行开发,只需使用左键和鼠标滚轮来模拟捏合/缩放/旋转。

现在我们知道了这一切,我们该如何实现用户需要滑动矩形才能得分的要求呢?实际上并不太难;即使使用指针事件也可以。首先,我们需要在初始化时进行区分:

if (shapeToInit is Ellipse)
{
    Holder.Tapped += Holder_Tapped;
}
else if (shapeToInit is Rectangle)
{
    Holder.PointerPressed += Holder_PointerPressed;
    Holder.PointerMoved += Holder_PointerMoved;
    Holder.PointerReleased += Holder_PointerReleased;
}

然后实现事件处理程序

// Handling Slide gesture
private const int MOVE_TO_COUNT = 20;
private Point _initalPoint;
private void Holder_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
    e.Handled = true; // do not propagate to other elements
    _initalPoint = e.GetCurrentPoint((UIElement)Holder.Parent).RawPosition;
}

void Holder_PointerMoved(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
    e.Handled = true; // do not propagate to other elements
    if (_initalPoint.X != 0 && _initalPoint.Y != 0)
    {
        var currentPoint = e.GetCurrentPoint((UIElement)Holder.Parent).RawPosition;
        HolderPosition.X = currentPoint.X - (Holder.Width / 2);
        HolderPosition.Y = currentPoint.Y - (Holder.Height / 2);

        if (Math.Abs(currentPoint.X - _initalPoint.X) >= MOVE_TO_COUNT ||
            Math.Abs(currentPoint.Y - _initalPoint.Y) >= MOVE_TO_COUNT)
        {
            Holder_PointerReleased(sender, e);
            Score();
        }
    }
}

void Holder_PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
    e.Handled = true; // do not propagate to other elements
    _initalPoint = new Point(0, 0);
}

// Handling simple tap
void Holder_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    e.Handled = true; // do not propagate to other elements
    Score();
}


//
private void Score()
{
    sb.Stop();

    // TODO: Implement more fancy "exit" effect
    Holder.Width = 0;
    Holder.Height = 0;

    // Score
    _tapped(this);
}

除了点击、长按和滑动之外,Windows 8 触摸指南还将轻扫(短距离滑动)、捏合、旋转和拉伸识别为“常见手势”。随着文章的不断完善,将添加更多形状和这些手势的实现。

摇我,碎我

- 陀螺仪解释和基本开发内容。

- 通过实现道具来演示陀螺仪的使用。

- 通过连续点击奖励用户“摇晃道具”。摇晃道具会摧毁屏幕上所有形状。

- 奖励在 10 个以上形状上执行摇晃道具的用户“滑动道具”。滑动道具允许用户通过倾斜设备将形状滑出屏幕。以这种方式摧毁的每个形状都获得双倍分数。

保持分数公平

- 探索绑定概念,用于在游戏进行中显示分数。

- 处理游戏结束事件和保存分数

- 异步简要介绍

- 开发简单的服务器端以接受高分,同时考虑到我们最终可能会有不同的游戏模式。

- 上传和下载数据

总结

尽管有些部分缺失,文章仍在撰写中,但我真心希望我能为您提供对 Windows 8 开发的扎实介绍。正如我在开篇时所说,我期待您的反馈——请帮助我并引导您希望本文发展方向。

资源

我将尝试列出与 Windows 8 开发相关的有趣网页链接。欢迎贡献。

历史

10月18日 - 初始版本

© . All rights reserved.