Silverlight动画基础二:动画与向量-弹珠小游戏

在上一篇中主要是简单介绍了向量在动画中的简单应用。下面使用向量来完成一个简单的游戏 Paddle Gam。最终运行效果图如下:   

image

Paddle Game有以下几个对象:

1.ball 用于运动的小球

2.paddle 用于控制小球不要弹出界外

3.wall 墙壁,当小球碰到墙壁后会反弹回来

4.PaddleGame 主容器

 

 

一、ball对象

首先来看看ball.xaml的代码,25*25的白色小球:

<Canvas Width="25" Height="25" x:Name="LayoutRoot">
<Ellipse Width="25" Height="25" Fill="#FFFFFFFF" Stroke="#FF000000"/>
</Canvas>

 
ball.xaml.cs代码:
public partial class ball : UserControl
{
//容器高度
public double rootHeight;
//X轴速度
public double VelocityX;
//Y轴速度
public double VelocityY;
//用于随机生成初始位置
private Random rng = new Random();

public ball()
{
InitializeComponent();
}

/// <summary>
/// 初始化小球的起始位置和速度
/// </summary>
public void Init()
{
VelocityX = 5;
VelocityY = rng.Next(1, 8);
Canvas.SetLeft(this, 65);
Canvas.SetTop(this,rng.Next( Convert.ToInt16(this.Height),Convert.ToInt16(rootHeight-this.Height)));
}
}

 二、paddle对象

下面为paddle.xaml代码,声明了一个20*100的矩形:

<Canvas x:Name="LayoutRoot">
<Rectangle x:Name="paddleRectangle" Width="20" Height="100" RadiusX="3" RadiusY="3"
Stroke="#FF888888" Fill="#FF494747"/>
</Canvas>

 

paddle.xaml.cs代码,用于捕捉焦点、实现鼠标拖动效果:

public partial class paddle : UserControl
{
//paddle移动速度
public double paddleYVelocity;
//容器高度
public double rootHeight;
//是否捕获鼠标
private bool isMouseCaptured;
//当前鼠标位置
private Point mousePosition;
private double oldY;
private double posY;

public paddle()
{
InitializeComponent();
this.MouseLeftButtonDown += new MouseButtonEventHandler(paddle_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(paddle_MouseLeftButtonUp);
this.MouseMove += new MouseEventHandler(paddle_MouseMove);
}

void paddle_MouseMove(object sender, MouseEventArgs e)
{
FrameworkElement item = sender as FrameworkElement;

if (isMouseCaptured)
{
//计算paddle的移动速度
oldY = posY;
posY = e.GetPosition(null).Y;
paddleYVelocity = (posY - oldY) / 2;

//根据鼠标移动距离,计算paddle的当前位置
double deltaV = e.GetPosition(null).Y - mousePosition.Y;
double newTop = deltaV + Canvas.GetTop(item);

//检测边界,当在碰到上下边时设置其位置为0和最大
if (newTop < 0)
newTop = 0;

if (newTop + this.Height > rootHeight)
newTop = rootHeight - this.Height;

item.SetValue(Canvas.TopProperty, newTop);
mousePosition = e.GetPosition(null);
}
}

void paddle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{

FrameworkElement item = sender as FrameworkElement;
//移除捕获鼠标
isMouseCaptured = false;
item.ReleaseMouseCapture();
mousePosition.Y = 0;
item.Cursor = null;
}

void paddle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement item = sender as FrameworkElement;
//保存鼠标当前位置
mousePosition = e.GetPosition(null);
//设置捕获焦点
isMouseCaptured = true;
item.CaptureMouse();
item.Cursor = Cursors.Hand;
}
}

 

 

三、wall对象

wall对象创建了一面红色的墙,无其他逻辑,代码如下:

<Canvas x:Name="brickWall" Width="64" Height="597" Background="#FFFFFFFF">
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="-1"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="61"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="123"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="185"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="247"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="309"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="371"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="433"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="495"/>
<Rectangle Width="20" Height="40" Fill="#FFCC1212" Canvas.Left="44" Canvas.Top="557"/>
<Rectangle Width="20" Height="30" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="0"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="32"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="94"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="156"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="218"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="280"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="342"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="404"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="466"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="528"/>
<Rectangle Width="20" Height="7" Fill="#FFCC1212" Canvas.Left="22" Canvas.Top="590"/>
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="0" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="62" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="124" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="186" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="248" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="310" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="372" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="434" Canvas.Left="0" />
<Rectangle Width="20" Height="60" Fill="#FFCC1212" Canvas.Top="496" Canvas.Left="0" />
<Rectangle Width="20" Height="39" Fill="#FFCC1212" Canvas.Top="558" Canvas.Left="0" />
</Canvas>

 

主要的几个对象已经创建完成,下面来实现关键代码。
 

四、PaddleGame对象

PaddleGame对象包含了游戏的主逻辑。主要功能:判断小球状态(位置),控制小球在画布中移动不用超出范围,

记录分数,控制主要逻辑。

PaddleGame.xaml代码:

<Canvas Height="600" Width="800" x:Name="LayoutRoot">
<Canvas.Resources>
<Storyboard x:Name="Move" Duration="00:00:00.00"/>
<!--隐藏消息窗动画-->
<Storyboard x:Name="hideMessages">
<DoubleAnimation Duration="00:00:00.50" To="0" Storyboard.TargetName="gameMessages"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"/>
<DoubleAnimation Duration="00:00:00.50" To="0" Storyboard.TargetName="gameMessages"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"/>
</Storyboard>
<!--现实消息窗动画-->
<Storyboard x:Name="showMessages">
<DoubleAnimation Duration="00:00:00.50" To="1" Storyboard.TargetName="gameMessages"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"/>
<DoubleAnimation Duration="00:00:00.50" To="1" Storyboard.TargetName="gameMessages"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"/>
</Storyboard>
</Canvas.Resources>
<Canvas.Background>
<LinearGradientBrush EndPoint="0.875,0.5" StartPoint="0.125,0.5">
<GradientStop Color="#FF030057"/>
<GradientStop Color="#FF020105" Offset="1"/>
</LinearGradientBrush>
</Canvas.Background>
<!--容器-->
<Canvas Height="600" Width="800" x:Name="gameElements"/>

<!--显示剩余小球数量-->
<TextBlock x:Name="msgRemaining" Width="146" Height="24" Canvas.Left="8" Canvas.Top="8" FontFamily="Arial,SimSun"
Foreground="#FFFFFFFF" Text="剩余小球 : 3" TextWrapping="Wrap"/>
<!--显示当前得分-->
<TextBlock x:Name="msgScore" Width="146" Height="24" Canvas.Left="155" Canvas.Top="8" FontFamily="Arial,SimSun"
Foreground="#FFFFFFFF" Text="得 分 : 0" TextWrapping="Wrap" />
<!--消息窗口-->
<Canvas Width="800" Height="600" x:Name="gameMessages" RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="msgBackground" Width="357" Height="258" Stroke="#FF000000" Canvas.Left="221.5" Canvas.Top="171"
RadiusX="12" RadiusY="12" IsHitTestVisible="False">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="1.04299998283386,1.04999995231628"
StartPoint="-0.0659999996423721,-0.061999998986721">
<GradientStop Color="#FFD4D3D3" Offset="0"/>
<GradientStop Color="#FF4D4D4D" Offset="0.96"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>

<TextBlock x:Name="msgGameText" Width="315" Height="217.5" Canvas.Left="242.5" Canvas.Top="191.25" TextWrapping="Wrap"
FontFamily="Arial,SimSun" FontSize="14" Foreground="#FF000000" />
<!--开始按钮-->
<Button Height="28" x:Name="btnPlay" Width="100" Content=" 开a 始? " Canvas.Top="380" Canvas.Left="350" Cursor="Hand"/>
</Canvas>
</Canvas>

 

下面是PaddleGamev.xaml.cs代码:

public partial class PaddleGame : Page
{

private int lives = 3;
private paddle gamePaddle = null;
private wall bricks = null;
private ball gameBall = null;
private double score = 0.0;

public PaddleGame()
{
InitializeComponent();

//初始场景,将各个元素添加到容器中,并设置其位置
gamePaddle = new paddle();
gamePaddle.SetValue(Canvas.LeftProperty, 44.00);
gamePaddle.SetValue(Canvas.TopProperty, 92.00);
gamePaddle.Visibility = Visibility.Collapsed;
gamePaddle.rootHeight = LayoutRoot.Height;
gameElements.Children.Add(gamePaddle);

bricks = new wall();
bricks.SetValue(Canvas.LeftProperty, 735.00);
bricks.SetValue(Canvas.TopProperty,-4.00);
gameElements.Children.Add(bricks);

gameBall = new ball();
gameBall.Visibility = Visibility.Collapsed;
gameBall.rootHeight = LayoutRoot.Height;
gameElements.Children.Add(gameBall);

msgGameText.Text = "小游戏·\n\n使用鼠标拖拽面板控制小球,保持小球在容器内运动.\n\n有三次机会.\n\n小球碰到墙壁将获得5分\n出界,你都将失去一个小球.";
btnPlay.Click += new RoutedEventHandler(btnPlay_Click);
hideMessages.Completed += new EventHandler(hideMessages_Completed);
Move.Completed += new EventHandler(Move_Completed);
}

void Move_Completed(object sender, EventArgs e)
{
//根小球的X/Y轴速度,设置小球位置
Canvas.SetLeft(gameBall, Canvas.GetLeft(gameBall) + gameBall.VelocityX);
Canvas.SetTop(gameBall, Canvas.GetTop(gameBall) + gameBall.VelocityY);

//检测上下边界 ,若到达边界则改变小球方向,并设置小球的位置在容器内
if (Canvas.GetTop(gameBall) <= 0)
{
Canvas.SetTop(gameBall, 0);
//改变方向
gameBall.VelocityY *= -1;
}
else if (Canvas.GetTop(gameBall) + gameBall.Height >= LayoutRoot.Height)
{
Canvas.SetTop(gameBall, LayoutRoot.Height - gameBall.Height);
gameBall.VelocityY *= -1;
}
//检测小球是否触碰到墙壁,若触碰到墙壁更改方向并设置其位置不超过墙壁位置
if (Canvas.GetLeft(gameBall) + gameBall.Width >= Canvas.GetLeft(bricks))
{
SetScore(5);
Canvas.SetLeft(gameBall, Canvas.GetLeft(bricks) - gameBall.Width);
gameBall.VelocityX *= -1;
}


//检测小球与paddle对象的位置
//检测小球的上下和左右位置是否在paddle的触碰范围内。
if (Canvas.GetTop(gameBall) >= Canvas.GetTop(gamePaddle) &&
Canvas.GetTop(gameBall) <= Canvas.GetTop(gamePaddle) +
gamePaddle.Height)
{
if (Canvas.GetLeft(gameBall) <= Canvas.GetLeft(gamePaddle) + gamePaddle.Width &&
Canvas.GetLeft(gameBall) >= Canvas.GetLeft(gamePaddle))
{

gameBall.VelocityY += gamePaddle.paddleYVelocity;
gameBall.VelocityX *= -1;
}
}

//若小球超出范围,调用nextball方法减少小球数量
if (Canvas.GetLeft(gameBall) <= -gameBall.Width)
{
Move.Stop();
nextBall();
}
else
{
Move.Begin();
}

}

void hideMessages_Completed(object sender, EventArgs e)
{
gameBall.Visibility = Visibility.Visible;
gamePaddle.Visibility = Visibility.Visible;
gameBall.Init();
Move.Begin();
}

void btnPlay_Click(object sender, RoutedEventArgs e)
{
hideMessages.Begin();
}

private void nextBall()
{
gameBall.Visibility = Visibility.Collapsed;
gamePaddle.Visibility = Visibility.Collapsed;

lives -= 1;
msgRemaining.Text = "剩余小球 : " + lives;
switch (lives)
{
case 2:
SetScore(-10);
msgGameText.Text = "加油,还有两次机会..";
break;
case 1:
SetScore(-10);
msgGameText.Text = "小心咯,就剩下一次机会 .";
break;
case 0:
SetScore(-10);
msgGameText.Text = "很遗憾,游戏结束了!! 你的最后得分是 :"+score.ToString("0.0");;
btnPlay.Visibility = Visibility.Collapsed;
break;
}
showMessages.Begin();
}

private void SetScore(double sc)
{
score += sc;
msgScore.Text = "得 分 : " + score.ToString("0.0");
}
}


自此全部代码完毕。这个示例主要是对使用向量和动画进行结合的实践。

运行效果演示地址:点击查看

 

总结:使用向量的特性来控制小球运动,并且根据元素之间的位置来进行简单的检测。

 

【注:本文技术论点源于《Foundation Silverlight 3 Animation》,个人理解可能存在差异,请参考原著】

你可能感兴趣的:(silverlight)