Live example:
http://www.adefwebserver.com/Richard/TheSeekerSiteV4/Default.html
I made a lot of changes to The Seeker, but probably the most important technically is the addition of Dependency Properties and more specifically Dependency Properties to support Animation.

The Simulation
First I’ll describe the changes to what goes on in ooNaWorld, where everything and everybody is an ooNaThing, or will be when I’m done. At any rate, Seekers chase and eat prey. Seekers start out as blue, change to red when chasing, turquoise in close pursuit, and black when they pause to eat. When a Seeker has eaten a specified number of prey it reproduces. Prey start out as pale green, turn purple when being pursued, and dark green after they have escaped pursuit.
ooBer Control Panel
All things ooNa are under the control of ooBer, master of the ooNaverse. You can communicate with ooBer through the ooBer Control Panel, which I’ll describe from left to right:
Left Mouse Button: Radio buttons determine whether left mouse click creates Prey or Seeker
Seekers/Prey: The panel just below Left Mouse Button shows number of current Seekers and Prey.
Seeker Reproduction: Determines whether Seekers reproduce and number of prey required to reproduce.
Eat Time: Number of seconds that Seeker will pause to eat.
Switch Prey: If clicked, Seeker will turn from current prey to chase one that is closer, otherwise it will chase current prey until caught.
Collision: Determine whether to do true hit-testing or declare a hit at a specified proximity. Proximity runs faster when there are a lot of players on the field.
Seekers: When Max is exceeded all Seekers are killed except the new-borne. Speed Min/Max sliders set speed range of newly created seekers.
Prey: If Max is exceeded, Auto-Create Prey does not fire. Auto-Create Prey determines whether to periodically create new prey, how often, and how many.
Clear Seekers/Prey Buttons: Clear all Seekers or Clear All Prey.
Dependency Properties and Animation:
Dependency properties and Animation go hand-in-hand because to animate a custom object, you create Dependency Properties that will be set repeatedly during the animation, such as Position and Heading. Then you create an animation, give it access to those properties, specify the duration and off you go. The animation will literally step your object through the sequence by adjusting the dependency properties you specified.
But let’s back up and talk about Animations of non-custom objects. For instance:
public static void MoveTo(FrameworkElement mover, Point destination, Duration duration, System.EventHandler callBack)
{
DoubleAnimation xAnimate = new DoubleAnimation();
xAnimate.Duration = duration;
xAnimate.To = destination.X;
DoubleAnimation yAnimate = new DoubleAnimation();
yAnimate.Duration = duration;
yAnimate.To = destination.Y;
Storyboard sbMove = new Storyboard();
sbMove.Duration = duration;
sbMove.Children.Add(xAnimate);
sbMove.Children.Add(yAnimate);
Storyboard.SetTarget(sbMove, mover);
Storyboard.SetTargetProperty(xAnimate, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTarget(yAnimate, mover);
Storyboard.SetTargetProperty(yAnimate, new PropertyPath("(Canvas.Top)"));
// When the animation is done, call this handler to re acquire the target and
// start the next animation
sbMove.Completed+=callBack;
sbMove.Begin();
}
The animation can use the existing FrameworkElement properties Canvas.Left and Canvas.Top to step the FrameworkElement object across the screen. Note that it’s actually two DoubleAnimations, one for each property, Canvas.Left and Canvas.Top, which are doubles, hence the name DoubleAnimation. For each step in the animation new values for Canvas.Left and Canvas.Top are calculated and used by the animation to set those properties.
This was the animation Seeker used up until this version. But I wanted to add rotation so at the same time I changed the position animation to use a PointAnimation, which repeatedly sets a Point dependency property to step the object through the animation.
Here’s the code for the PointAnimation to do the same move. All I have to do is set the duration, the end point, the target object (seeker) and the dependency property the Animation will set to move seeker step by step across the screen
PointAnimation _seekerPositionAnimation = new PointAnimation();
_seekerPositionAnimation.Duration = seeker.ooNaStoryboard.Duration;
_seekerPositionAnimation.To = WayPoint;
Storyboard.SetTarget(_seekerPositionAnimation, seeker);
Storyboard.SetTargetProperty(_seekerPositionAnimation, new PropertyPath("Position"));
Here’s the Seeker class Position Property
// The property must use SetValue and GetValue.
public Point Position
{
set { this.SetValue(PositionProperty, value); }
get { return (Point)this.GetValue(PositionProperty); }
}
// Here’s where we register the dependency property, note it’s outside any method. We pass the name of the property “Position”, the type, Point, the typeof the owning object, Seeker, and a pointer to an event handler to be called when the property changes.
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position",
typeof(Point),
typeof(Seeker),
new PropertyMetadata(PositionChanged));
// Here’s where we do whatever it is we need to do when the property changes. We have to do it through an event handler because the Dependency Property mechanism won’t allow it to be called from the Property setter itself. As you can see, this gives me a hook so I can perform code at every step of the animation.
private static void PositionChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Seeker seeker = sender as Seeker;
if (seeker == null)
return;
seeker.SetValue(Canvas.LeftProperty, ((Point)e.NewValue).X);
seeker.SetValue(Canvas.TopProperty, ((Point)e.NewValue).Y);
if (seeker.MoveLogic != null)
{
Prey prey = seeker.AcquiredTarget;
Double xDistance = (seeker.Position.X + seeker.ActualWidth / 2) - (prey.Position.X + prey.ActualWidth / 2);
Double yDistance = (seeker.Position.Y - (prey.Position.Y + prey.ActualHeight / 2));
// Sum the square of the two opposite sides
Double oppSides_Squared = (xDistance * xDistance) + (yDistance * yDistance);
// The straight line distance is the square root of the result
Double distance = Math.Pow(oppSides_Squared, 0.5);
seeker.GoToState(distance < 50 ? seeker.CloseToPrey.Name : seeker.PursuingPrey.Name, false);
if (seeker.MoveLogic.InterceptProximity > 0 && seeker.AcquiredTarget != null)
{
if (seeker.MoveLogic != null && distance < seeker.MoveLogic.InterceptProximity)
{
seeker.MoveLogic.InterceptTarget(seeker);
}
}
else if (Common.TargetCollision(sender as Seeker) && seeker.MoveLogic != null)
{
seeker.MoveLogic.InterceptTarget(seeker);
}
}
}
At the same time, I run the animation to do the rotation:
DoubleAnimation _seekerHeadingAnimation = new DoubleAnimation();
_seekerHeadingAnimation.Duration = seeker.ooNaStoryboard.Duration;
_seekerHeadingAnimation.To = bClockwise ? newHeading : newHeading - 360;
Storyboard.SetTarget(_seekerHeadingAnimation, seeker);
Storyboard.SetTargetProperty(_seekerHeadingAnimation, new PropertyPath("Heading"));
Seeker has a dependency property named “Heading”. The animation above will step the Heading property through all the values between whatever it’s set to right now up through the value I specified in _seekerHeadingAnimation.To. That Heading property will set the Seeker Rotate Transformation to each of those values, stepping the rotation through gradual steps at the same time as the position is being adjusted in gradual steps.
Visual State
In the PositionChanged Method above, I call seeker.MoveLogic.InterceptTarget(seeker) if I detect a collision between seeker and prey. Let me show you that method and briefly discuss VisualState at the same time. First of all, notice that I save the seeker position and angle before I stop the Storyboard, then use the saved values to reset them. That’s because when you call Stop on storyboard it resets the animation. There may be a way to avoid this and I just haven’t found it yet.
Next is some syntax that may not seem familiar. EatingPrey is a Seeker VisualState. I can use the name of the VisualState to access the Storyboard, which lets me set the duration and set an event handler to be called when the storyboard completes. Maybe you’re saying “What storyboard?”. A Storyboard, as far as I know is always included in a VisualState . Running the storyboard, including the animations that are also generated when the VisualState is created, is how the transition is made from one VisualState to another. The beauty is that just through the name of the VisualState I’m able to access the associated StoryBoard and even add an event handler.
The code called by seeker.MoveLogic.InterceptTarget(seeker) is from the ooNaMove class, which implements IooNaMove:
public void InterceptTarget(Seeker seeker)
{
if (seeker.Eating)
return;
seeker.Eating = true;
seeker.AcquiredTarget.Die();
Point startPosition = seeker.Position;
Double startAngle = seeker.Heading;
seeker.ooNaStoryboard.Stop();
seeker.Position = startPosition;
seeker.Heading = startAngle;
seeker.EatingPrey.Storyboard.Completed += new EventHandler(seeker.EatingPrey_Completed);
seeker.EatingPrey.Storyboard.Duration = new Duration(TimeSpan.FromSeconds(seeker.SecondsToEat));
seeker.GoToState(seeker.EatingPrey.Name, false);
}
Here’s the “Eating Prey” VisualState defined in Seeker.xaml
<VisualState x:Name="EatingPrey">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:05.0010000" Storyboard.TargetName="VisualMe" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="00:00:00" Value="#FF000016"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
And to finish off the cycle, here’s the event handler that’s called when the EatingPrey Storyboard completes. Note the first thing I do is un-subscribe from the event, again using the name of the VisualState to get to the storyboard. After I digest my meal, I go to my normal state and get rid of my recent kill in order to prepare for the next one.
public void EatingPrey_Completed(object sender, EventArgs e)
{
this.EatingPrey.Storyboard.Completed -= EatingPrey_Completed;
Eaten++;
if (Eaten >= PreyQuota)
{
if (PreyQuota > 0)
{
Point createPoint = Position;
if (createPoint.X < 300)
createPoint.X += 50;
else
createPoint.X -= 50;
if (createPoint.Y < 300)
createPoint.Y += 50;
else
createPoint.Y -= 50;
if (OOBER != null)
OOBER.CreateSeeker(createPoint);
}
Eaten = 0;
}
GoToState(Normal.Name, false);
Eating = false;
AcquiredTarget = null;
}
Download code:
http://www.adefwebserver.com/Richard/Seekerv4.0.zip