Fall Fury: Part 10 - Charms

Charms

With the release of the Windows 8 operating system, applications are now able to easily integrate with each other and have a unified way to control their workflow through unique system-wide contracts called Charms. Out of the multitude of available options, FallFury leverages two charms—Share contract and Settings. This article describes how the integration is implemented.

Check out the video for this article at http://channel9.msdn.com/Series/FallFury/Part-10-Charms .  For a complete, offline version of this series, you may download a nicely formatted PDF of all the articles.

clip_image002

The Share Contract

When a user achieves a specific score in the game, he might decide to share it with his social circle. In Charms, that’s the purpose of the Share contract, which integrates directly with the OS.

Windows Store applications have the capability to expose their sharing capabilities and register as a share target. For example, if there is a Twitter client out, it can register itself as an app through which content can be shared. FallFury, on the other hand, acts as a consumer that aggregates existing share targets and lets the user pick the one through which he wants to let the message out:

clip_image004

Let’s take a look at how this process is built in the code-behind. The core is located in DirectXPage.xaml.cpp—the class responsible for XAML content manipulation in FallFury. First and foremost, you need to get the current instance of the DataTransferManager class:

auto dataTransferManager = Windows::ApplicationModel::DataTransfer::DataTransferManager::GetForCurrentView();

Consider this a proxy that allows you to pass content between your app and other Windows Store apps that are executed in the context of the same sandbox. As the instance is obtained, hook it to the DataRequested event handler that will handle the scenario where the user invoked the sharing capability:

dataTransferManager->DataRequested += ref new 
    TypedEventHandler<Windows::ApplicationModel::DataTransfer::DataTransferManager^,
    Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs^>(this, &DirectXPage::OnShareDataRequested);

 

In the OnShareDataRequested, specify the information that goes into the sharing popup:

void DirectXPage::OnShareDataRequested(Windows::ApplicationModel::DataTransfer::DataTransferManager^ manager,Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs^ params)
{
    auto request = params->Request;
    request->Data->Properties->Title = "Share Score";
    request->Data->Properties->Description = "Tell your friends how much you scored in [DEN'S PROJECT]!";
    request->Data->SetText("I just scored " + StaticDataHelper::Score.ToString() + " in [DEN'S PROJECT]! Beat me! http://dennisdel.com");
}

 

From that point, the control is in the user’s hand. The application cannot force the share, so unless you implement a direct API hook to a social service, the Share charm will only expose the endpoints available for sharing and will let you set the shareable content. You also don’t have to worry about the way the content will be shared—that will be handled by the target application:

clip_image006

You can show the popup triggered by the Share charm from your application without having the user open the Charms Bar. To do this, call the ShowShareUI method:

void DirectXPage::ShareTopScore_Selected(MenuItem^ sender, Platform::String^ params)
{
    Windows::ApplicationModel::DataTransfer::DataTransferManager::ShowShareUI();
}

 

This is exactly what the Share button does in the screenshot above. You should make this behavior predictable.

The Settings Charm

As you just saw, integrating basic sharing capabilities in FallFury is not too complicated. Working with settings is also a fairly easy task, though it involves some XAML work. While with sharing capabilities the work focused mostly on OS-based endpoints and application-specific popups, settings allow for full control over how they’re displayed.

For all Windows Store applications, settings should be handled via the Settings charm and not through dedicated application screens. Consider which settings that directly affect the user experience might be changed in FallFury:

clip_image008

  • Music and SFX – the user can enable or disable music and sound effects, as well as control their volume.
  • Accelerometer – depending on personal preferences, the user might decide to disable the accelerometer (it is enabled by default). The accelerometer can also be inverted—if the device is tilted to the right, the character will move to the left and vice-versa. Last but not least, even with dynamic screen rotation enabled on the device, the user can disable that rotation on the application level and lock the screen to one orientation, such as portrait or landscape.
  • Character Movement – the character can be easily controlled via touch (swipe) or mouse. This behavior is enabled by default, but if the user decides to only use the mouse to direct shells, he can easily disable this feature here.

As seen in the image above, the operating system provides the basic shell used to list the possible settings. Once one option is selected, however, further UI displays are delegated to the developer.

As with the share UI, the settings UI can be shown to the user from the application and not from the Charms Bar. Here is how to do this:

void DirectXPage::Settings_Selected(MenuItem^ sender, Platform::String^ params)
{
    SettingsPane::GetForCurrentView()->Show();
}

 

SettingsPane is the core class that handles the settings display. It does not control how settings are stored or activated. When the main page loads, you need to make sure that you hook the current SettingsPane to a CommandRequested event handler. It will be triggered when the Settings capability is invoked:

SettingsPane::GetForCurrentView()->CommandsRequested += ref new TypedEventHandler<SettingsPane^, SettingsPaneCommandsRequestedEventArgs^>(this, &DirectXPage::OnSettingsRequested);

OnSettingsRequested is the function where the core setting selections are defined and hooked to their own event handlers:

void DirectXPage::OnSettingsRequested(Windows::UI::ApplicationSettings::SettingsPane^ settingsPane, Windows::UI::ApplicationSettings::SettingsPaneCommandsRequestedEventArgs^ eventArgs)
{
    if (m_renderer->CurrentGameState == GameState::GS_PLAYING)
            StaticDataHelper::IsPaused = true;
    
    UICommandInvokedHandler^ handler = ref new UICommandInvokedHandler(this, &DirectXPage::OnSettingsSelected);
    
        SettingsCommand^ generalCommand = ref new SettingsCommand("musicSfx", "Music & SFX", handler);
        eventArgs->Request->ApplicationCommands->Append(generalCommand);
    
    SettingsCommand^ accelerometerCommand = ref new SettingsCommand("accelerometer", "Accelerometer", handler);
    eventArgs->Request->ApplicationCommands->Append(accelerometerCommand);
    
    SettingsCommand^ charMovementCommand = ref new SettingsCommand("charMovement", "Character Movement", handler);
    eventArgs->Request->ApplicationCommands->Append(charMovementCommand);
}

Each SettingsCommand is an item in the list displayed in the settings pane. When one is selected, OnSettingsSelected is called:

void DirectXPage::OnSettingsSelected(Windows::UI::Popups::IUICommand^ command)
{
    if (command->Id->ToString() == "musicSfx")
    {
        stkMusicSfx->Width = 346.0f;
        grdSubMusicSfx->Height = m_renderer->m_renderTargetSize.Height;
        stkMusicSfx->IsOpen = true;
    }
    else if (command->Id->ToString() == "accelerometer")
    {
        stkAccelerometerSettings->Width = 346.0f;
        grdAccelerometerSettings->Height = m_renderer->m_renderTargetSize.Height;
        stkAccelerometerSettings->IsOpen = true;
    }
    else if (command->Id->ToString() == "charMovement")
    {
        stkCharacterMovement->Width = 346.0f;
        grdCharacterMovement->Height = m_renderer->m_renderTargetSize.Height;
        stkCharacterMovement->IsOpen = true;
    }

    WindowActivationToken = Window::Current->Activated += ref new WindowActivatedEventHandler(this, &DirectXPage::OnWindowActivated);
}

Looking back at OnSettingsRequested, each command has a string identifier. When a command is invoked, that string identifier is returned through the IUICommand instance in the Id property. Based on that, I decided which popups to open. Since each has a similar structure, I am going to cover the implementation of just one—Music & SFX.

Here is what the end result looks like:

clip_image010

The panel on the left is a Popup, with two ToggleButton controls used to enable or disable generic music and sound effects. There are also two Slider controls that are used to adjust the volume. The XAML for the above layout looks like this:

<Popup HorizontalAlignment="Right" IsLightDismissEnabled="True" x:Name="stkMusicSfx" >
    <Grid Background="Black" x:Name="grdSubMusicSfx"  Width="346">
        <Grid.Transitions>
            <TransitionCollection>
                <RepositionThemeTransition />
            </TransitionCollection>
        </Grid.Transitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="120"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="24,12,0,0" >
            <Button Margin="0" VerticalAlignment="Center" x:Name="dismissAudioSettings" Click="dismissAudioSettings_Click" Style="{StaticResource BackButtonStyle}"></Button>
            <TextBlock Margin="12,0,0,12" Height="Auto" VerticalAlignment="Center" Text="Music &amp; SFX" Style="{StaticResource SubheaderTextStyle}"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Row="1" Margin="24,24,0,0">
            <StackPanel>
                <TextBlock Text="Music" Style="{StaticResource BodyTextStyle}"></TextBlock>
                <TextBlock  Width="346" Text="This includes the theme track and level background music." Style="{StaticResource CaptionTextStyle}" TextWrapping="Wrap" Margin="0,12,12,12" ></TextBlock>
                <ToggleSwitch x:Name="tglMusic" Toggled="tglMusic_Toggled" IsOn="{Binding ElementName=XAMLPage,Path=MusicEnabled}" Margin="-6,0,0,0"></ToggleSwitch>
            </StackPanel>

            <StackPanel Margin="0,24,0,0">
                <TextBlock Text="Music Volume" Style="{StaticResource BodyTextStyle}"></TextBlock>
                <Slider x:Name="sldMusicVolume" ValueChanged="sldMusicVolume_ValueChanged" Value="{Binding ElementName=XAMLPage,Path=MusicVolume, Mode=TwoWay}" Minimum="0" Maximum="100" Margin="0,0,12,0"></Slider>
            </StackPanel>

            <StackPanel Margin="0,24,0,0">
                <TextBlock Text="Sound Effects" Style="{StaticResource BodyTextStyle}"></TextBlock>
                <TextBlock Width="346" Text="Includes sounds played during the game (e.g. explosions)." Style="{StaticResource CaptionTextStyle}" TextWrapping="Wrap" Margin="0,12,12,12" ></TextBlock>
                <ToggleSwitch x:Name="tglSFX" Toggled="tglSFX_Toggled" IsOn="{Binding ElementName=XAMLPage,Path=SFXEnabled}" Margin="-6,0,0,0"></ToggleSwitch>
            </StackPanel>

            <StackPanel Margin="0,24,0,0">
                <TextBlock Text="SFX Volume" Style="{StaticResource BodyTextStyle}"></TextBlock>
                <Slider x:Name="sldSFXVolume" ValueChanged="sldSFXVolume_ValueChanged" Value="{Binding ElementName=XAMLPage,Path=SFXVolume, Mode=TwoWay}" Minimum="0" Maximum="100" Margin="0,0,12,0"></Slider>
            </StackPanel>

        </StackPanel>

    </Grid>
</Popup>

For every Popup instance used for settings, make sure that IsLightDismissEnabled is set to true. This allows the user to dismiss the panel with a touch outside its boundaries, just like the stock system panels. Other than that, you are working with the standard XAML control set and can include virtually anything in your settings.

Notice, that the switches and sliders are bound to internal properties, such as SFXEnabled and MusicEnabled, that perform the binding via dependency property references:

DependencyProperty^ DirectXPage::_musicEnabled = DependencyProperty::Register("MusicEnabled", 
bool::typeid, DirectXPage::typeid, nullptr);
DependencyProperty^ DirectXPage::_sfxEnabled = DependencyProperty::Register("SFXEnabled", 
bool::typeid, DirectXPage::typeid, nullptr);

 

The properties themselves are declared in the DirectXPage header file:

static property DependencyProperty^ SFXVolumeProperty
{
    DependencyProperty^ get() { return _sfxVolume; }
}
property int SFXVolume
{
    int get() { return (int)GetValue(SFXVolumeProperty); }
    void set(int value) 
    { 
        SetValue(SFXVolumeProperty, value);
    }
}

static property DependencyProperty^ MusicVolumeProperty
{
    DependencyProperty^ get() { return _musicVolume; }
}
property int MusicVolume
{
    int get() { return (int)GetValue(MusicVolumeProperty); }
    void set(int value) 
    { 
        SetValue(MusicVolumeProperty, value);
    }
}

Let’s take a quick look at how settings are stored. I have a class called SettingsHelper that allows me to save, read, and check if specific settings exist. Here is the implementation:

#include "pch.h"
#include "SettingsHelper.h"

using namespace Windows::Storage;
using namespace Coding4Fun::FallFury::Helpers;

SettingsHelper::SettingsHelper(void)
{
}

void SettingsHelper::Save(Platform::String^ key, Platform::Object^ value)
{
    ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
    auto values = localSettings->Values;

    values->Insert(key, value);
}

Platform::Object^ SettingsHelper::Read(Platform::String^ key)
{
    ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
    auto values = localSettings->Values;

    return values->Lookup(key);
}

bool SettingsHelper::Exists(Platform::String^ key)
{
    ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
    auto values = localSettings->Values;

    return values->HasKey(key);
}

 

It is clear that storage and retrieval heavily relies on the ApplicationDataContainer class, the container class for local settings that eliminates the need for the developer to create his own setting files, instead delegating this task to the OS and utilizing a centralized storage for all Windows Store applications.

A typical scenario that utilizes the class above is executed when the toggle that manages the sound effects is switched:

void DirectXPage::tglSFX_Toggled(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
    SettingsHelper::Save("sfxEnabled", tglSFX->IsOn);
    SFXEnabled = tglSFX->IsOn;
    AudioManager::IsSFXStarted = SFXEnabled;

    if (SFXEnabled)
    {
        AudioManager::AudioEngineInstance.StartSFX();
    }
    else
    {
        AudioManager::AudioEngineInstance.SuspendSFX();
    }
}

 

The Boolean value above will be automatically serialized and stored. The files will be located at C:\Users\YOUR_USER_NAME\AppData\Local\Packages\PACKAGE_ID\Settings\settings.dat:

 

clip_image012

Conclusion

Implementing sharing through the OS channel in Windows 8 is extremely easy seeing as the developer does not necessarily have to worry about connecting the app to third-party API endpoints. Instead, the user controls the sharing, allowing flexibility of choice without requiring a major addition to the existing code base. It’s hard to predict which services might appear, and modifying the app to support each one of them would be next to impossible otherwise.

You can read more about settings in Windows Store applications here.

Tags:

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.