Silverlight动画基础二:动画与向量-弹珠小游戏
在上一篇中主要是简单介绍了向量在动画中的简单应用。下面使用向量来完成一个简单的游戏 Paddle Gam。最终运行效果图如下:
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》,个人理解可能存在差异,请参考原著】