MMORPG programming in Silverlight Tutorial (9)KeyFrame Animation
In the last chapter, I introduce A* algorithm, so the object can find the shortest path itself, I print the path array on the canvas. This chapter I will animate the object to pass through the path array from start to the end.
We still use the demo in the last chapter.
1st, add an ellipse named player into the canvas, as the sprite in our demo.
//the sprite private Ellipse player; private void InitPlayer() { player = new Ellipse() { Fill = new SolidColorBrush(Colors.Blue), Width = gridSize, Height = gridSize }; Carrier.Children.Add(player); //init start point Canvas.SetLeft(player, gridSize); Canvas.SetTop(player, 5 * gridSize); }
2nd, add obstruction in the method InitMatrix.
void InitMatrix() { for (int y = 0; y < matrix.GetUpperBound(1); y++) { for (int x = 0; x < matrix.GetUpperBound(0); x++) { matrix[x, y] = 1; } } for (int y = 3; y < 30; y++) { matrix[3, y] = 0; rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Red); rect.Width = gridSize; rect.Height = gridSize; Carrier.Children.Add(rect); Canvas.SetLeft(rect, 3 * gridSize); Canvas.SetTop(rect, y * gridSize); } for (int y = 3; y < 20; y++) { matrix[24, y] = 0; rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Red); rect.Width = gridSize; rect.Height = gridSize; Carrier.Children.Add(rect); Canvas.SetLeft(rect, 24 * gridSize); Canvas.SetTop(rect, y * gridSize); } for (int i = 0; i < 18; i++) { matrix[i, 12] = 0; rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Red); rect.Width = gridSize; rect.Height = gridSize; Carrier.Children.Add(rect); Canvas.SetLeft(rect, i * gridSize); Canvas.SetTop(rect, 12 * gridSize); } for (int i = 12; i < 17; i++) { matrix[17, i] = 0; rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Red); rect.Width = gridSize; rect.Height = gridSize; Carrier.Children.Add(rect); Canvas.SetLeft(rect, 17 * gridSize); Canvas.SetTop(rect, i * gridSize); } for (int i = 3; i < 18; i++) { matrix[i, 16] = 0; rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Red); rect.Width = gridSize; rect.Height = gridSize; Carrier.Children.Add(rect); Canvas.SetLeft(rect, i * gridSize); Canvas.SetTop(rect, 16 * gridSize); } }
3rd, execute these 2 methods above in our constructor.
public MainPage() { InitializeComponent(); //init matrix InitMatrix(); //init sprite InitPlayer(); }
4. Add keyframe in the method Carrier_MouseLeftButtonDown, I have written enough comment in the code, which can help you understand more clearly.
private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Point p = e.GetPosition(Carrier); //scale down the coordinate of start and end int start_x = (int)Canvas.GetLeft(player) / gridSize; int start_y = (int)Canvas.GetTop(player) / gridSize; start = new Point(start_x, start_y); int end_x = (int)p.X / gridSize; int end_y = (int)p.Y / gridSize; end = new Point(end_x, end_y); //use A* algorithm IPathFinder pathFinder = new PathFinderFast(matrix); pathFinder.Formula = HeuristicFormula.Manhattan; pathFinder.HeavyDiagonals = true; pathFinder.HeuristicEstimate = 0; var path = pathFinder.FindPath(start, end); if (path == null) { MessageBox.Show("The path doesn't exist"); return; } //define keyframe array Point[] framePosition = new Point[path.Count]; for (int i = path.Count - 1; i >= 0; i--) { //fill keyframe array from the start of the path array, and enlarge the coordinate framePosition[path.Count - 1 - i] = new Point(path[i].X * gridSize, path[i].Y * gridSize); } //create storyboard Storyboard storyboard = new Storyboard(); int cost = 100; //spend 100ms per grid(20*20) //create animation frame by frame in X-coordinate DoubleAnimationUsingKeyFrames keyFramesAnimationX = new DoubleAnimationUsingKeyFrames(); //all the spending time = path.Count * cost keyFramesAnimationX.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost)); Storyboard.SetTarget(keyFramesAnimationX, player); Storyboard.SetTargetProperty(keyFramesAnimationX, new PropertyPath("(Canvas.Left)")); //create animation frame by frame in Y-coordinate DoubleAnimationUsingKeyFrames keyFramesAnimationY = new DoubleAnimationUsingKeyFrames(); keyFramesAnimationY.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost)); Storyboard.SetTarget(keyFramesAnimationY, player); Storyboard.SetTargetProperty(keyFramesAnimationY, new PropertyPath("(Canvas.Top)")); for (int i = 0; i < framePosition.Count(); i++) { //add keyframe in X-coordinate LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame(); keyFrame.Value = i == 0 ? Canvas.GetLeft(player) : framePosition[i].X; keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i)); keyFramesAnimationX.KeyFrames.Add(keyFrame); //add keyframe in Y-coordinate keyFrame = new LinearDoubleKeyFrame(); keyFrame.Value = i == 0 ? Canvas.GetTop(player) : framePosition[i].Y; keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i)); keyFramesAnimationY.KeyFrames.Add(keyFrame); } storyboard.Children.Add(keyFramesAnimationX); storyboard.Children.Add(keyFramesAnimationY); //start the storyboard storyboard.Begin(); //trace the moving by white points for (int i = path.Count - 1; i >= 0; i--) { rect = new Rectangle() { Fill = new SolidColorBrush(Colors.White), Width = 5, Height = 5 }; Carrier.Children.Add(rect); Canvas.SetLeft(rect, path[i].X * gridSize); Canvas.SetTop(rect, path[i].Y * gridSize); } }
More or less, the principle is that, when we click on the canvas, we will start and end point at once.
We add keyFramesAnimationX and keyFramesAnimationY in the storyboard, they are Timeline in the x and y coordinate.
//create animation frame by frame in X-coordinate DoubleAnimationUsingKeyFrames keyFramesAnimationX = new DoubleAnimationUsingKeyFrames(); //all the spending time = path.Count * cost keyFramesAnimationX.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost)); Storyboard.SetTarget(keyFramesAnimationX, player); Storyboard.SetTargetProperty(keyFramesAnimationX, new PropertyPath("(Canvas.Left)")); //create animation frame by frame in Y-coordinate DoubleAnimationUsingKeyFrames keyFramesAnimationY = new DoubleAnimationUsingKeyFrames(); keyFramesAnimationY.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost)); Storyboard.SetTarget(keyFramesAnimationY, player); Storyboard.SetTargetProperty(keyFramesAnimationY, new PropertyPath("(Canvas.Top)"));
……(ignore some code)
storyboard.Children.Add(keyFramesAnimationX); storyboard.Children.Add(keyFramesAnimationY);
Then we add keyframe in the Timeline:
for (int i = 0; i < framePosition.Count(); i++) { //add keyframe in X-coordinate LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame(); keyFrame.Value = i == 0 ? Canvas.GetLeft(player) : framePosition[i].X; keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i)); keyFramesAnimationX.KeyFrames.Add(keyFrame); //add keyframe in Y-coordinate keyFrame = new LinearDoubleKeyFrame(); keyFrame.Value = i == 0 ? Canvas.GetTop(player) : framePosition[i].Y; keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i)); keyFramesAnimationY.KeyFrames.Add(keyFrame); }
Finally, we print all the path element in white color. Notice that the first element in the path array is the end point of the path., so we need to iterate the path array from end to start.
OK, press Ctrl+F5, click on the canvas. The original point of the ellipse is A. When I click on the point B, the ellipse begins moving. When the ellipse passes through the point C, we click on the other point D, the ellipse will change direction at once, it dues to the features of timeline and keyframe.
Summary: This chapter introduce how to integrate A* algorithm and KeyFrame, so we can control the animation freely.
Next chapter, I will implement how to add all these techniques in our game engine. Please focus on it.
Chinese friend, you can also visit this Chinese blog if you feel difficult to read English, http://www.cnblogs.com/alamiye010/archive/2009/06/17/1505346.html, part of my article is base on it.
Demo download: http://silverlightrpg.codeplex.com/releases/view/40978
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
2008-02-27 WF本质论 读书心得 3 活动的执行