MMORPG programming in Silverlight Tutorial (13)Perfect moving mechanism

    Now it is time to resolve the issue left in chapter 8, when there are no obstructions between the distinction and original place, the sprite still use A* algorithm to find path, rather than moving between these 2 point directly, as follows:

image

    So we need to modify moving mechanism. When we click on the map, the sprite’ action is default to move in straight path. And we create a new thread to detect if the sprite meet obstruction during his moving all the time. This time we use CompositionTarget.Rendering, because we have used DispatcherTimer in the sprite’s own animation, we want to distinguish these 2 different threads.

    If the sprite find an obstruction during his moving, he will use A* algorithm until he walk around it. Then he will move straightly again. This process will last until the sprite arrive the distinction.

    All the analysis above can be divided into 3 steps in our demo:

    1st, pick the color of the sprite’s distinction, if it is not an obstruction, move in a straight way; otherwise, no actions.

//record the sprite's distination
Point target;

private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Point p = e.GetPosition(Carrier);

    //if the click point is not an obstruction
    if (PickColor(copyMap, (int)p.X, (int)p.Y, Carrier.ActualWidth) != Colors.Black)
    {
        target = p;

        //move in a straight path
        StraightMove(p); 
    }
}

 

    2nd, detect obstruction in a new thread all the time:

public MainPage()
{
    InitializeComponent();
    ……    
    CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
    int x = Convert.ToInt32(Canvas.GetLeft(sprite) + spriteCenterX);
    int y = Convert.ToInt32(Canvas.GetTop(sprite) + spriteCenterY);

    var color = PickColor(copyMap, x, y, Carrier.ActualWidth);

    //if meet obstruction, use A* algorithm
    if (color == Colors.Black)
    {
        AStarMove(target);
    }

    //trace the path in white, if not necessary, we need to mark them in our release version
    var rect = new Rectangle();
    rect.Fill = new SolidColorBrush(Colors.White);
    rect.Width = 5;
    rect.Height = 5;
    Carrier.Children.Add(rect);
    Canvas.SetLeft(rect, x);
    Canvas.SetTop(rect, y);
}

    The functions StraightMove and AStarMove are implemented as follows, please be careful the sprite’s coordinate when set Canvas’s Left and Top properties.

private void StraightMove(Point p)
{
    //create storyboard
    storyboard = new Storyboard();

    //create animation frame by frame in X-coordinate
    DoubleAnimation doubleAnimation = new DoubleAnimation()
    {
        From = Canvas.GetLeft(sprite),
        To = p.X - spriteCenterX,
        Duration = new Duration(TimeSpan.FromMilliseconds(1000)),
    };
    Storyboard.SetTarget(doubleAnimation, sprite);
    Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Left)"));
    storyboard.Children.Add(doubleAnimation);

    //create animation frame by frame in Y-coordinate
    doubleAnimation = new DoubleAnimation()
    {
        From = Canvas.GetTop(sprite),
        To = p.Y - spriteCenterY,
        Duration = new Duration(TimeSpan.FromMilliseconds(1000)),
    };
    Storyboard.SetTarget(doubleAnimation, sprite);
    Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Top)"));
    storyboard.Children.Add(doubleAnimation);

    //start the storyboard
    storyboard.Begin();
}

private void AStarMove(Point p)
{
    //scale down the coordinate of start and end
    int start_x = (int)(Canvas.GetLeft(sprite) + spriteCenterX) / gridSize;
    int start_y = (int)(Canvas.GetTop(sprite) + spriteCenterY) / 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;
    List<PathFinderNode> path = pathFinder.FindPath(start, end);

    //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();
    keyFramesAnimationX.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost));  //all the spending time = path.Count * cost
    Storyboard.SetTarget(keyFramesAnimationX, sprite);
    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, sprite);
    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(sprite) : (framePosition[i].X - spriteCenterX);
        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(sprite) : (framePosition[i].Y - spriteCenterY);
        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();
}

 

    OK, press Ctrl+F5, we can see the effect as we expected before:

image 

    Notice, to trace the sprite's moving, we add many white point in the canvas, which will reduce the performance of the game engine. So in the actual programming, please mark it if not necessary, you can find it in the method CompositionTarget_Rendering.

 

Summary: This chapter introduce a perfect moving animation. Our game engine is more and more robust.

    Next chapter, I will introduce how to Object-Oriented Programming in Silverlight. I will separate the sprite from the main logic. 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

posted @ 2010-03-01 16:06 Jianqiang Bao Views(2422) Comments(6) Edit 收藏

 回复 引用 查看   
#1楼2010-03-12 22:26 | erichan      
看完深蓝的文章,估计很少有(中国)人来关心英文版本,似乎即使老外关注的也不是很多,回复的人有限啊。
一点建议:与其重复深蓝的工作,不如做些互补的东西。例如深蓝不打算涉及通信部分,楼主何不在这方面做些文章?相信人气一定会旺起来。老外也不会再质疑这不是真正的mmorpg了。

 回复 引用 查看   
#2楼2010-03-14 20:46 | 深蓝色右手      
@erichan
我原先的教程不是Silverlight,并且还是存在很多BUG,包哥做得很不错了。

 回复 引用 查看   
#3楼2010-03-28 21:19 | 衣不如新      
学习,多谢楼主
 回复 引用 查看   
#4楼2010-05-26 18:50 | DC_KK      
想看深蓝教程的sl 实现 又遇到E文 痛苦痛哭啊
 回复 引用 查看   
#5楼2010-06-24 22:20 | banban      
中文中文!
 回复 引用 查看   
#6楼2010-08-14 19:54 | ALLENWANG      
Hi, jianqiang, long time no see, how are you, and I'd like to read your article for silverlight, oh, it's interesting and well-written:)