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

Soccerlight 世界杯 2010

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (78投票s)

2010年6月29日

CPOL

9分钟阅读

viewsIcon

143324

downloadIcon

2727

一款使用 Silverlight 和 VS 2008 开发的足球游戏。

Soccerlight

目录

引言

这是我的第一个 Silverlight 游戏。几个月前,我萌生了制作与正在进行的 2010 年 FIFA 世界杯相关的东西的想法,但有一段时间,我一直在问自己能做什么?幸运的是,我童年的回忆帮助我回答了这个问题。

这款游戏是无数辛勤工作的结晶,融合了学习和试错的方法。幸运的是,当你真正专注于某件事时,学习曲线就开始变得容易。当道路铺平了,你就可以奔跑了。

背景

在巴西,有一个非常著名的游戏叫做“Futebol de Botão”(按钮足球),或者“Futebol de Mesa”(桌面足球),值得一提。

20 世纪初,巴西的男孩们,就像世界上许多其他地方的孩子一样,都是足球的狂热爱好者。到处都是足球俱乐部,广播播放着足球比赛,将这项娱乐传播到整个巴西。根据维基百科的说法,有人想出了这个主意,制作一个桌面足球游戏,场地可以是任何光滑的平面,球员就是放在桌子上的纽扣。这些纽扣是衣服上用的那种大纽扣。让当时的穷妈妈们头疼不已,不得不缝上新的纽扣,桌面足球成为了巴西儿童中最流行的游戏之一(尤其是当他们被禁止在户外踢真足球的时候……)

除了使用衣物纽扣,人们还尝试过其他材料,例如用椰子壳和骨头制成的部件。守门员只是用沙子或重物填充的火柴盒。但最终,工业界开始用塑料材料制作纽扣,这成为了该游戏的商业版本的标准。

鉴于如今大多数孩子更喜欢电脑游戏,在本文中,我试图重现那些塑料纽扣的感觉和氛围。

您可以通过查看我上传到下面链接的 YouTube 视频来快速了解游戏。

系统要求

要使游戏正常工作,如果您还没有 VS 2008 和 Silverlight 3,可以下载以下内容。

Soccerlight 解决方案

Solution

图 1:解决方案结构

Visual Studio 2008 解决方案主要由 Soccerlight 项目组成,其中包含 Silverlight 本身以及其他辅助项目,如下表所示。

项目 描述
Silverlight 这是 Silverlight 项目本身。
Silverlight.Controls 这个 Silverlight 类库项目包含我在项目中使用的部分自定义控件。
Silverlight.Core 这个项目包含部分游戏逻辑和模型类。
Silverlight.Web 这个项目是启动项目,包含应用程序的入口页面。

主菜单

主菜单包含一个表格,其中列出了 2010 年 FIFA 世界杯的所有 8 个小组。

想法是选择一支国家队并开始比赛。然后您将必须严格按照 2010 年世界杯的赛程进行比赛。

为了实现这个队伍表格,我并没有过多使用 XAML。我主要通过编程来实现。您在下图看到的由 GridTextBlockImage 元素组成。我知道我本可以使用带有自定义模板的 ListBox 元素,但是……我想要更多的自由。通过以编程方式构建视觉元素,我认为我可以更好地控制动画、变换等。

Intro

图 2:主菜单

下面的函数 GenerateGroups 创建了所有 32 支队伍,并将它们分成 8 个小组。

void GenerateGroups()
{
    for(int i = 0; i < 8; i++)
    {
        Border brd = new Border();
        brd.CornerRadius = new CornerRadius(5);
        brd.Margin = new Thickness(2);
        brd.SetValue(Grid.ColumnProperty, i % 4);
        brd.SetValue(Grid.RowProperty, i / 4);

        LinearGradientBrush lgb = new LinearGradientBrush();
        lgb.StartPoint = new Point(0, 0);
        lgb.EndPoint = new Point(1, 1);
        lgb.GradientStops = new GradientStopCollection();
        lgb.GradientStops.Add(new GradientStop() 
          { Offset = 0.0, Color = Color.FromArgb(255, 0, 0, 0)});
        lgb.GradientStops.Add(new GradientStop() 
          { Offset = 0.5, Color = Color.FromArgb(255, 30, 30, 30)});
        lgb.GradientStops.Add(new GradientStop() 
          { Offset = 1.0, Color = Color.FromArgb(255, 40, 40, 40)});
        brd.Background = lgb;

        lgbEven.StartPoint = new Point(0, 0);
        lgbEven.EndPoint = new Point(1, 1);
        lgbEven.GradientStops = new GradientStopCollection();
        lgbEven.GradientStops.Add(new GradientStop() 
              { Offset = 0.0, Color = Color.FromArgb(255, 0, 0, 0) });
        lgbEven.GradientStops.Add(new GradientStop() 
              { Offset = 0.5, Color = Color.FromArgb(255, 30, 30, 30) });
        lgbEven.GradientStops.Add(new GradientStop() 
              { Offset = 1.0, Color = Color.FromArgb(255, 80, 80, 80) });

        Grid grdGroup = new Grid();
        grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(28) });
        grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
        grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });

        TextBlock txtGroupID = new TextBlock()
        {
            Text = ((char)(i + 65)).ToString(),
            Foreground = new SolidColorBrush(Colors.White),
            FontSize = 18,
            FontWeight = FontWeights.Bold
        };
        txtGroupID.SetValue(Grid.ColumnProperty, 0);
        txtGroupID.SetValue(Grid.RowProperty, 0);
        txtGroupID.SetValue(Grid.RowSpanProperty, 4);
        txtGroupID.SetValue(VerticalAlignmentProperty, VerticalAlignment.Center);
        txtGroupID.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);
        grdGroup.Children.Add(txtGroupID);

        for (int j = 0; j < 4; j++)
        {
            Grid grdTeam = new Grid();
            grdTeam.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            grdTeam.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            grdTeam.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(25) });
            grdTeam.SetValue(Grid.ColumnProperty, 1);
            grdTeam.SetValue(Grid.ColumnSpanProperty, 2);
            grdTeam.SetValue(Grid.RowProperty, j);
            grdTeam.HorizontalAlignment = HorizontalAlignment.Stretch;
            grdTeam.VerticalAlignment = VerticalAlignment.Center;
            grdTeam.Width = 120;

            Team team = 
              GameHelper.Instance.TeamsDictionary[GameHelper.Instance.TeamCodes[i * 4 + j]];
            Image img = new Image()
            {
                Source = new BitmapImage(new Uri(string.Format(
                         @"http://www.fifa.com/imgml/flags/reflected/m/{0}.png", 
                         team.TeamID.ToLower()), UriKind.Absolute)),
                
                Width = 28.5,
                Height = 25.0,
                VerticalAlignment = VerticalAlignment.Center
            };
            img.SetValue(Grid.ColumnProperty, 0);
            img.SetValue(Grid.RowProperty, j);
            img.VerticalAlignment = VerticalAlignment.Top;
            img.HorizontalAlignment = HorizontalAlignment.Stretch;
            img.Clip = new RectangleGeometry() { Rect = 
                       new Rect(new Point(0, 0), new Point(28.5, 14)) };
            img.Tag = team.TeamID;
            TranslateTransform tf = new TranslateTransform()
            {
                 X = 0,
                 Y = 6
            };
            img.RenderTransform = tf;

            TextBlock txt = new TextBlock()
            {
                Text = team.TeamName,
                Foreground = new SolidColorBrush(Colors.White)
            };
            txt.SetValue(Grid.ColumnProperty, 1);
            txt.SetValue(Grid.RowProperty, j);
            txt.VerticalAlignment = VerticalAlignment.Center;
            txt.HorizontalAlignment = HorizontalAlignment.Stretch;
            txt.Tag = team.TeamID;

            grdTeam.Tag = team.TeamID;
            grdTeam.Children.Add(img);
            grdTeam.Children.Add(txt);
            grdTeam.MouseEnter += new MouseEventHandler(team_MouseEnter);
            grdTeam.MouseLeave += new MouseEventHandler(team_MouseLeave);
            grdTeam.MouseLeftButtonUp += 
                    new MouseButtonEventHandler(team_MouseLeftButtonUp);

            grdGroup.Children.Add(grdTeam);
        }

        brd.Child = grdGroup;

        grdGroupsContainer.Children.Add(brd);
    }
}
图 3:IntroMenu.xaml.cs 文件中的 GenerateGroups() 函数

Soccerlight 比赛场地

如果您在主菜单中选择了英格兰队,接下来您将被重定向到以下视图,这当然是英格兰在世界杯上的第一场比赛(英格兰对阵美国,6 月 12 日,在鲁斯坦堡)。

请注意,两支球队都以传统的 3-5-2 阵型开始比赛,其中 3 名后卫,5 名中场,2 名前锋。虽然这是所有球队在游戏中的默认阵型,但为选定的球队选择其他阵型并不难,因为阵型只是一个整数数组。

public Team()
{
    Formation = new int[] {1, 3, 5, 2};
}
图 4:Team 类构造函数,显示了如何为每支球队设置 3-5-2 阵型。

请注意,上面的代码片段中的数组以1开头。这是因为我们必须考虑到守门员。

Table

图 5:Soccerlight 比赛场地,显示了英格兰对阵美国的 3-5-2 阵型。

请注意,下面的列表中的场地线条和圆圈实际上不是图像,而是由 BorderEllipse 元素构成的。另请注意,四个角上有四分之一圆,可以通过使用 RectangleGeometry 来裁剪普通圆(参见下面的 Ellipse.Clip 标签)来轻松创建,从而只显示您想要的四分之一部分。

<Grid x:Name="LayoutRoot" 
       MouseLeftButtonUp="LayoutRoot_MouseLeftButtonUp" 
       VerticalAlignment="Center" ...
    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="colLeftEscapeArea" Width="53"/>
        <ColumnDefinition x:Name="colLeftPosts" Width="53"/>
        <ColumnDefinition x:Name="colLeftGoalArea" Width="70"/>
        <ColumnDefinition x:Name="colLeftPenaltyArea" Width="280"/>
        <ColumnDefinition x:Name="colHalfWay" Width="280"/>
        <ColumnDefinition x:Name="colRightPenaltyArea" Width="70"/>
        <ColumnDefinition x:Name="colRightGoalArea" Width="53"/>
        <ColumnDefinition x:Name="colRightPosts" Width="53"/>
        <ColumnDefinition x:Name="colMenu" Width="32"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition x:Name="rowTopEscapeArea" Height="50"/>
        <RowDefinition x:Name="rowTopFieldLine" Height="108"/>
        <RowDefinition x:Name="rowTopPenaltyArea" Height="67"/>
        <RowDefinition x:Name="rowTopGoalArea" Height="153"/>
        <RowDefinition x:Name="rowBottomGoalArea" Height="67"/>
        <RowDefinition x:Name="rowBottomPenaltyArea" Height="108"/>
        <RowDefinition x:Name="rowBottomFieldLine" Height="50"/>
    </Grid.RowDefinitions>
    <Border Grid.Column="0" Grid.Row="0" 
            Grid.ColumnSpan="8" Grid.RowSpan="1" 
            Background="DarkGreen" ...
    <Border Grid.Column="0" Grid.Row="1" 
            Grid.ColumnSpan="1" Grid.RowSpan="5" Background="DarkGreen" ...
    <Border Grid.Column="1" Grid.Row="1" 
            Grid.ColumnSpan="3" Grid.RowSpan="5" BorderBrush="White" ...
    <Border Grid.Column="4" Grid.Row="1" 
            Grid.ColumnSpan="3" Grid.RowSpan="5" BorderBrush="White" ...
    <Border Grid.Column="1" Grid.Row="3" 
            Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
    <Border Grid.Column="6" Grid.Row="3" 
            Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
    <Border Grid.Column="1" Grid.Row="2" 
            Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
    <Border Grid.Column="1" Grid.Row="2" 
            Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
    <Border Grid.Column="5" Grid.Row="2" 
            Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
    <Border Grid.Column="7" Grid.Row="1" 
            Grid.ColumnSpan="1" Grid.RowSpan="5" Background="DarkGreen" ...
    <Border Grid.Column="0" Grid.Row="6" 
            Grid.ColumnSpan="8" Grid.RowSpan="1" Background="DarkGreen" ...
    <StackPanel Grid.Column="1" Grid.Row="1" 
            Grid.ColumnSpan="6" Grid.RowSpan="5" Margin="8, 8, 0, 0" ...
        <Image Source="../Images/Soccerlight.png" Stretch="UniformToFill">
            <Image.RenderTransform>
                <ScaleTransform ScaleX="0.25" ScaleY="0.25"/>
            </Image.RenderTransform>
        </Image>
    </StackPanel>
    <Grid Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="6" Grid.RowSpan="5" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="92"/>
            <ColumnDefinition Width="115"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="15"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="115"/>
            <ColumnDefinition Width="92"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <Ellipse Grid.Column="0" Grid.Row="0" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="15,15,30,30"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="-15" Y="-15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="10" Grid.Row="0" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="0,15,15,15"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="15" Y="-15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="0" Grid.Row="6" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="15,0,15,15"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="-15" Y="15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="10" Grid.Row="6" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="0,0,15,15"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="15" Y="15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="4" Grid.Row="2" Grid.ColumnSpan="3" Grid.RowSpan="3" 
        StrokeThickness="2" Stroke="White"/>
        <Ellipse Grid.Column="5" Grid.Row="3" Fill="White"/>
        <Ellipse Grid.Column="2" Grid.Row="2" Grid.ColumnSpan="1" Grid.RowSpan="3" 
        StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="57.5,0,115,115"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="-57.5" Y="0"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="8" Grid.Row="2" Grid.ColumnSpan="1" 
                 Grid.RowSpan="3" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="0,0,57.5,115"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="57.5" Y="0"/>
            </Ellipse.RenderTransform>
        </Ellipse>
    </Grid>
    <Canvas x:Name="rootCanvas"/>
    <Border Grid.Column="0" Grid.Row="3" 
             Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
        <Grid>
            <Grid.Background>
                <ImageBrush ImageSource="../Images/goalnet.png" Stretch="UniformToFill"/>
            </Grid.Background>
        </Grid>
    </Border>
    <Border Grid.Column="7" Grid.Row="3" 
              Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
        <Grid>
            <Grid.Background>
                <ImageBrush ImageSource="../Images/goalnet.png" Stretch="UniformToFill"/>
            </Grid.Background>
        </Grid>
    </Border>
    <Border x:Name="brdBallStrengthContainer" 
              Grid.Column="8" Grid.Row="4" Grid.ColumnSpan="1" ...
        <Grid>
            <Border x:Name="brdStrength" CornerRadius="4" Margin="8,56,8,8">
                <Border.Background>
                    <LinearGradientBrush>
                        <GradientStop Color="Red" Offset="0.0"/>
                        <GradientStop Color="Orange" Offset="0.5"/>
                        <GradientStop Color="Yellow" Offset="1.0"/>
                    </LinearGradientBrush>
                </Border.Background>
            </Border>
            <Image x:Name="imgBallStrength" Source="../Images/Jabulani.png" 
                      VerticalAlignment="Top" Margin="0,32,0,0">
                <Image.RenderTransform>
                    <RotateTransform x:Name="rtBallStrength" 
                         CenterX="11.5" CenterY="11.5" Angle="30">
                    </RotateTransform>
                </Image.RenderTransform>
            </Image>
        </Grid>
    </Border>
    <Grid x:Name="grdStadiumScreen" MaxWidth="800" 
             MaxHeight="180" Grid.Column="1" Grid.Row="0"...
        <Grid.Clip>
            <RectangleGeometry Rect="0,0,800,180"/>
        </Grid.Clip>
        <TextBlock Foreground="White" FontSize="120" 
              Text="GOALLLLLL!!!" TextAlignment="Center" Margin="0">
        <TextBlock.RenderTransform>
            <TranslateTransform x:Name="lettersXTranslate" X="0"/>
        </TextBlock.RenderTransform>
        </TextBlock>
        <Image Source="../Images/mask.png" Stretch="Fill" Opacity="0.75"/>
        <Grid x:Name="grdBrightness" Background="Black" Opacity="1.00"/>
    </Grid>
    <Grid.RenderTransform>
        <ScaleTransform ScaleX="0.868" ScaleY="0.8700"/>
    </Grid.RenderTransform>
</Grid>

得分控制

得分控制显示了比赛剩余时间、球队昵称、一个闪烁的球指示当前比赛球队,以及……比分。

剩余时间由 DispatcherTimer 对象控制,该对象每秒触发一次 Tick 事件。这会传播到得分控制,以增加总剩余时间。

在真实的足球比赛中,有两场 45 分钟的半场,中间休息 15 分钟。然而,在 Soccerlight 中,当计时器达到 30 分钟时,比赛就结束了。

闪烁的球是一个有用的指示器,可以显示当前比赛的球队。

ScoreControl

图 6:得分控制

球门

每个球门由三个边框界定:两边各一个,后方一个。如果球撞击这些边框,它会像撞到墙壁一样反弹。此外,在球门的正面两侧,还有门柱,它们就像真实的圆柱形门柱一样,使球反弹,就像撞击圆形物体一样。

此外,球门还有一个透明的球网,为游戏增添了酷炫逼真的外观和感觉。

规则很简单:每当球进入球门时,应用程序都会更新得分控制,以增加进攻球队的比分。

Goals

图 7:球门:注意边框、门柱和细节的球网

Jabulani

Jabulani

图 8:Jabulani 球

正如一些读者可能知道的,Jabulani 是 FIFA 世界杯的创新官方用球,所以我决定在 Soccerlight World Cup 中也使用它。

一些球员对 FIFA 杯的 Jabulani 提出了抱怨,因为他们报告了“奇怪”的运动轨迹和不可预测的方向变化,而另一些球员则声明了对该球的喜爱。(另一方面,目前还没有 Soccerlight 球员对这个球提出抱怨……)

在 Soccerlight 中,涉及球的物理计算与涉及球员的物理计算没有区别(除了球员具有更大的直径和更大的摩擦系数)。事实上,球和球员都继承自同一个基类 Discoid

public class Ball : Discoid
{
    public class Player : Discoid
    {

参与者

每支球队有 11 名球员。在现实世界中,每名球员可能有不同的角色,但在 Soccerlight 中,它们被以相同的方式对待。例如,前锋和守门员之间的唯一区别是守门员离球门更近,而前锋的初始位置更靠近对方球门。也许在未来的版本中,我会定义不同的技能,例如,一名技术娴熟的前锋在射门时可能比其他普通球员更精准。

Players

图 9:比赛中的球员:笑一个……

球员信息栏

每当您选择一名球员时,应用程序都会显示一个球员信息栏。该栏包含球员的基本信息:号码、姓名和照片。

每名 Soccerlight 球员都有号码、姓名和照片。号码和姓名在应用程序中硬编码,但照片是从 FIFA 网站获取的,如下面的代码片段所示。

imgPlayer.ImageSource = new BitmapImage(new Uri(string.Format(
    "http://pt.fifa.com/imgml/tournament/worldcup2010/players/xl/{0}.png", 
    teamPlayer.ID), UriKind.Absolute));

下面的列表负责显示球员信息栏。

<Grid x:Name="grdPlayerInfo" HorizontalAlignment="Center" 
             VerticalAlignment="Bottom" Visibility="Collapsed">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="8"/>
        <ColumnDefinition Width="400"/>
        <ColumnDefinition Width="200"/>
    </Grid.ColumnDefinitions>
    <Border Margin="8,8,0,64" CornerRadius="8" 
             Width="75" Height="100" 
             Background="#C0000000">
        <Border.RenderTransform>
            <TransformGroup>
                <TranslateTransform X="4" Y="4"/>
            </TransformGroup>
        </Border.RenderTransform>
    </Border>
    <Border Margin="8,8,0,64" CornerRadius="8" 
             Width="75" Height="100">
        <Border.Background>
            <RadialGradientBrush>
                <GradientStop Offset="0.0" Color="#00000000"/>
                <GradientStop Offset="9.0" Color="#20000000"/>
                <GradientStop Offset="1.0" Color="#40000000"/>
            </RadialGradientBrush>
        </Border.Background>
        <Border.RenderTransform>
            <TransformGroup>
                <TranslateTransform X="4" Y="4"/>
            </TransformGroup>
        </Border.RenderTransform>
    </Border>
    <Border Margin="8,8,0,64" CornerRadius="8" 
             Width="75" Height="100">
        <Border.Background>
            <ImageBrush x:Name="imgPlayer"/>
        </Border.Background>
    </Border>
    <StackPanel Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,64">
        <Border BorderBrush="Black" BorderThickness="2" CornerRadius="4">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0.0" Color="Yellow"/>
                    <GradientStop Offset="0.5" Color="Yellow"/>
                    <GradientStop Offset="0.5" Color="Gold"/>
                    <GradientStop Offset="1.0" Color="Goldenrod"/>
                </LinearGradientBrush>
            </Border.Background>
            <Border.RenderTransform>
                <SkewTransform AngleX="-10"/>
            </Border.RenderTransform>
            <StackPanel Orientation="Horizontal">
                <TextBlock x:Name="numPlayer" Text="" Foreground="Black" 
                FontSize="22" FontWeight="Bold" Margin="32,0,0,0"/>
                <TextBlock Text=" - " Foreground="Black" FontSize="22" 
                FontWeight="Bold"/>
                <TextBlock x:Name="txtPlayerName" Text="" Foreground="Black" 
                FontSize="22" FontWeight="Bold" Margin="0,0,0,0"/>
            </StackPanel>
        </Border>
    </StackPanel>
</Grid>

PlayerInfo

图 10:球员信息栏

力量控制条

力量控制条是您用来校准射门力量的工具。您将 Jabulani 球在条上推得越高,射门的力量就越大。

StrengthControl

图 11:力量控制条

每次用户点击力量控制条时,力量都会被重新定义。这是通过以下代码实现的。

private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    Point point = e.GetPosition(rootCanvas);

    if ((point.X > strengthPointNW.X) &&
        (point.X < strengthPointSE.X) &&
        (point.Y > strengthPointNW.Y) &&
        (point.Y < strengthPointSE.Y))
    {
        e.Handled = true;
        double relativeY = strengthPointSE.Y - point.Y;
        currentGame.Team1BallStrength = ((strengthPointSE.Y - point.Y) / 
               (strengthPointSE.Y - strengthPointNW.Y)) * 100.0;

        imgBallStrength.Margin = new Thickness(0, point.Y - 
            strengthPointNW.Y - imgBallStrength.ActualHeight / 2.0, 0, 0);
        brdStrength.Margin = new Thickness(8, point.Y - 
            strengthPointNW.Y + imgBallStrength.ActualHeight / 2.0, 8, 8);
    }

体育场屏幕

体育场屏幕的作用只是显示一支出线球队。就是这么简单。

StadiumScreen

图 12:体育场屏幕

体育场屏幕有三种不同的动画。首先是 translateAnimation,它将“GOOAL”字符串从右向左平移。然后是 lettersOpacityAnimation,它使屏幕上的字母淡入淡出。最后是 screenOpacityAnimation,负责淡出整个体育场屏幕元素。

sbStadiumScreen = new Storyboard()
{
    Duration = new Duration(new TimeSpan(0, 0, 0, 10))
};

DoubleAnimation translateAnimation = new DoubleAnimation()
{
    From = 800,
    To = 0,
    Duration = new Duration(new TimeSpan(0, 0, 0, 3))
};

Storyboard.SetTarget(translateAnimation, lettersXTranslate);
Storyboard.SetTargetProperty(translateAnimation, new PropertyPath("X"));

DoubleAnimation lettersOpacityAnimation = new DoubleAnimation()
{
    From = 0.8,
    To = 1.0,
    Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500)),
    AutoReverse = true,
    RepeatBehavior = RepeatBehavior.Forever
};

Storyboard.SetTarget(lettersOpacityAnimation, grdBrightness);
Storyboard.SetTargetProperty(lettersOpacityAnimation, new PropertyPath("Opacity"));

DoubleAnimation screenOpacityAnimation = new DoubleAnimation()
{
    From = 1.0,
    To = 0.0,
    BeginTime = new TimeSpan(0, 0, 0, 0),
    Duration = new Duration(new TimeSpan(0, 0, 0, 4))
};

sbStadiumScreen.Children.Add(translateAnimation);
sbStadiumScreen.Children.Add(lettersOpacityAnimation);
sbStadiumScreen.Children.Add(screenOpacityAnimation);

Storyboard.SetTarget(screenOpacityAnimation, grdStadiumScreen);
Storyboard.SetTargetProperty(screenOpacityAnimation, new PropertyPath("Opacity"));

愿望清单和已知问题

游戏中的一些问题以及一些期望的改进,我将在未来的版本中进行更正/实现。

  • 游戏仍然不处理犯规。起初,这似乎很容易实现,但根据犯规发生的位置,犯规后您可能需要重新安排球员,以免他们互相碰撞等。
  • 仍然没有淘汰赛阶段的比赛。也许我会为其他比赛生成随机结果,如果您的球队一路获胜,您将从 16 强晋级到四分之一决赛、半决赛,并最终进入决赛。
  • 实现一个在线的、基于服务的、人对人的版本游戏将会很酷。我认为 WCF 服务可以做到。

最终思考

如果您读到这里,我想感谢您的耐心。请告诉我您对这款应用的喜好。欢迎提出批评和建议,我愿意根据您的反馈改进这篇文章和应用程序。

历史

  • 2010-06-29:第一个版本。
© . All rights reserved.