This is episode 13 of Creating a Hidden Object Game is Silverlight 3. In this episode, we will add a hint feature to the game to help the players when they can’t find an item. This will require various animations and a custom behavior.
The hint feature can be segmented into three parts:
- Recharging hint button
- Hint overlay image with animation
- HintBehavior to randomly position the hint overlay image
Hint Button

To make the hint button, we will use an image of a laptop, a TextBlock (hintTextBlock), and a ProgressBar (progressBar) wrapped in a Canvas (hintCanvas):

The idea is that the TextBlock will contain the text “HINT” and act as a button to trigger the hint feature. When the TextBlock is clicked, the TextBlock is hidden and the ProgressBar shown. This is accomplished by adding a HintStates group to the main UserControl:

When the HintState is active, the TextBlock is shown and its IsHitTestVisible property is set to true so that it can be clicked. When the RechargeState is active, the TextBlock’s Opacity property is set to 0 and its IsHitTestVisible property is set to false so that it can’t be clicked.
To set the RechargeState, we add a GoToStateAction to the TextBlock:

A storyboard is added to change the value of the ProgressBar to indicate that the hint is recharging.
<Storyboard x:Name="RechargingStoryboard">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="progressBar"
Storyboard.TargetProperty="(RangeBase.Value)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:10" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
In this example, the storyboard will recharge in 10 seconds. For the game, the recharge duration should be somewhere between 30 seconds and 2 minutes.
This storyboard is started using a ControlStoryboardAction on the TextBlock:

The change from RechargeState to HintState is handled by the GoToStateAction and the StoryboardCompletedTrigger waiting on RechargingStoryboard. So as soon as the recharging animation ends, then the Hint button displays again.

Hint Overlay

The hint overlay was created in Expression Design and consists of 10 starbursts or flares set in a circular pattern. After the image is added to the project, drag it onto the LayoutRoot Canvas and locate it “off screen” (Left = 500, Top = -300). Set the ZIndex of the image to 99 so that it will be over any item on the game screen, but always under the cursor image.
When the hint TextBlock is clicked, two storyboards are started. The ShowHintStoryboard changes the opacity from 0% to 80% in 2 seconds and then auto reverses back to 0% over the next 2 seconds. The RotateHintStoryboard uses a RotateTransform to rotate the overlay 360 degrees over 4 seconds.
<Storyboard x:Name="ShowHintStoryboard" AutoReverse="True">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="hintFlareImage"
Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:02" Value="0.8"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="RotateHintStoryboard">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="hintFlareImage"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:04" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
To start the storyboards, use the ControlStoryboardAction with the EventTrigger:


Hint Behavior
The last thing we need to do is figure out where to put the hint overlay image. To do this, we will use a behavior that exposes a ShowHint command as well as a HintItems collection and a HintOverlayName property:

In the same way that the MouseCursorBehavior exposes the CursorName property to allow selection of the cursor, the HintBehavior exposes the HintOverlayName so that we can select the hint overlay image. An EventTrigger causes the ShowHint command to fire when the HINT TextBlock is clicked.
The HintItems collection contains one HintItem object for each item that can have a hint. The HintItem object contains a TargetName property to identify the Path or object representing the item and an X and Y variance for the location of the overlay.

The HintBehavior uses two NameResolver instances (see episode 12). The first NameResolver changes the HintOverlayName into a reference to the overlay image control. The second NameResolver is used once a HintItem is randomly picked to see if the object still exists and if so gets a reference to it. Even though a HintItem exists for all clickable items in the hidden object game, it may no longer exist in the visual tree as it could have been removed by the RemoveElementAction as discussed in episode 4.
When the ShowHint command is executed, the private OnShowHint method is called. This is the heart of the HintBehavior:
private void OnShowHint()
{
DependencyObject item = null;
if (!this.IsHintOverlayNameSet)
return;
FrameworkElement hintOverlay = HintOverlay as FrameworkElement;
//mix up the order of item names
HintItems.Randomize();
for (int index = 0; index < HintItems.Count; index++)
{
this.ItemResolver.Name = HintItems[index].TargetName;
item = this.ItemResolver.Object;
if (item != null)
{
double itemX = (double)item.GetValue(Canvas.LeftProperty);
double itemWidth = (double)item.GetValue(FrameworkElement.ActualWidthProperty);
double itemY = (double)item.GetValue(Canvas.TopProperty);
double itemHeight = (double)item.GetValue(FrameworkElement.ActualHeightProperty);
double newX = RandomWithVariance(itemX + (itemWidth / 2) - (hintOverlay.ActualWidth / 2), HintItems[index].OriginXVariance);
double newY = RandomWithVariance(itemY + (itemHeight / 2) - (hintOverlay.ActualHeight / 2), HintItems[index].OriginYVariance);
hintOverlay.SetValue(Canvas.LeftProperty, newX);
hintOverlay.SetValue(Canvas.TopProperty, newY);
break;
}
}
}
If the HintOverlayName is not set we exit the method, otherwise we get a reference to it. We then randomize the order of the items in the HintItems list. This is done using an extension method called Randomize(). Since some items named in the list may no longer exist on the Canvas, we do a null check after we access an item in the list and resolve it. If the item exists, then we determine the location of the item with its width and height so that we can center the overlay image over the item. The RandomWithVariance method uses the OriginXVariance and OriginYVariance values set on HintItem to make sure that the overlay image is over the item but that the item is not necessarily exactly centered.
The hint feature of our hidden object game is fairly simple once we break it into its three main components and work on them individually. Stay tuned for the next episode of Creating a Hidden Object Game in Silverlight 3.
Source Code
Demo