Creating Desired State Configuration with Azure Bicep

Many times you’ll run into situation that you would want your installation package to install the app automatically when you deploy your VM to Azure. DSC is the right tool for that, and there’s plenty of samples with ARM templates how you can do that. When you turn to Bicep, you won’t find any complete samples, but you need to try to collect from crumbles of information the necessary pieces, and it will take some time to figure out how you can do that. As I went through that path, I thought it would be nice to put all that in one place for the next one who need to do the same thing.

Create the DSC configuration

First thing you need, is a valid DSC configuration. Let’s create one using 7zip installation package. Create a new file called MyDSC1.ps1 with the following content:

Configuration InstallApp
{
    Import-DscResource -ModuleName PsDesiredStateConfiguration

    Node 'localhost'
    {
        Package InstallApp
        {
            Ensure = 'Present'
            Name = '7-Zip 19.00 (x64 edition)'
            Path = 'https://www.7-zip.org/a/7z1900-x64.msi'
            ProductId = '23170F69-40C1-2702-1900-000001000000'
        }
    }
}

Things worth mentioning here are the name of the configuration, InstallApp which we need later, and ProductIdProductId is something you can get with PowerShell from installed apps on your computer, and the Name parameter needs to match also the name which is visible in Apps & Features -list of installed apps, so you can’t put anything random there. If you are not running MSI package but EXE file, you can leave the ProductId field empty.

Azure doesn’t like ps1 files, but wants to have a zip archive instead. The recommended way to create it is to use PowerShell command Publish-AzVMDscConfiguration which is part of the Az.Compute -PowerShell module. If you don’t have that installed, run (and select A as your option when it asks):

Install-Module -Name Az.Compute -AllowClobber

Now that the module is installed (in case it wasn’t already), you can then create the zip archive with:

Publish-AzVMDscConfiguration .\MyDSC1.ps1 -OutputArchivePath '.\MyDSC1.zip'

Now that you have a zip archive including a ps1-file, you need to head to Azure portal, and create a Storage Account. After you have created one, open the Storage Account, under ‘Data Storage’, select ‘Containers’ and create new Container called ‘installers’ and upload the MyDSC1.zip to that container. Next open the file, and create SAS token for it, and copy the https link to notepad for later use.

Create the Bicep for the VM

Next we dig down to Bicep, and see how you insert VM extension to VM script. Lets see how the script would look and then dissect it a bit. Here we have a VM and just behind it we define the extension:

resource vm 'Microsoft.Compute/virtualMachines@2021-03-01' = {
  name: vmName
...
}
resource extDSC 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = {
  parent: vm
  name: 'Microsoft.Powershell.DSC'
  location: 'westeurope'
  properties: {
    autoUpgradeMinorVersion: true
    publisher: 'Microsoft.Powershell'
    type: 'DSC'
    typeHandlerVersion: '2.83'
    settings: {
      ModulesUrl: '<YOUR SAS TOKEN HERE, eg- https://mydsctest2021.blob.${environment().suffixes.storage}/installers/MyDSC1.zip?sp...'
      ConfigurationFunction: 'MyDSC1.ps1\\InstallApp'
      WmfVersion: 'latest'
      Privacy: {
        DataCollection: 'Enable'
      }
    }
    protectedSettings: {}
  }
}

You notice that we tie the extension to the machine by setting the parent to point to the VM resource. This also handles the DependsOn behind the scenes. typeHandlerVersion is just the version of the Poweshell.DSC module we are using. ModulesUrl is where we set the SAS token you created, but do notice the ${environment().suffixes.storage} which replaces the blob storage url. This is one of the Bicep rules, you will notice a warning if you don’t replace that part of the SAS, saying you are not allowed to have that URL in Bicep. The ConfigurationFunction defines that we have a configuration file called MyDSC1.ps1 which includes Configuration called InstallApp inside it, and that is what should be executed.

Now lets take a look of a complete Bicep file which you can use to test deploy VM and necessary other resources, with DSC which installs 7Zip:

// Parameters
param location string = 'westeurope'
param adminUsername string = 'azureAdmin'
@secure()
param adminPassword string

// Variables
var vnetName = 'myVnet'
var nsgName = 'myVM-nsg'
var nsgId = resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', nsgName)
var vnetId = resourceId(resourceGroup().name, 'Microsoft.Network/virtualNetworks', vnetName)
var subnetName = 'default'
var subnetRef = '${vnetId}/subnets/${subnetName}'

var subnetPrefix = '10.0.0.0/26'
var publicIpAddressName  = 'myVM-ip'
var vmName = 'myVM'
var addressPrefix  = [
  '10.0.0.0/25'
]

// Resources
resource nic 'Microsoft.Network/networkInterfaces@2018-10-01' = {
  name: 'myNIC'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: subnetRef
          }
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', publicIpAddressName)
          }
        }
      }
    ]
    enableAcceleratedNetworking: true
    networkSecurityGroup: {
      id: nsgId
    }
  }
  dependsOn: [
    nsg
    vnet
    publicIP
  ]
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2019-02-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: [
      {
        name: 'RDP'
        properties: {
          protocol: 'Tcp'
          sourcePortRange: '*'
          destinationPortRange: '3389'
          sourceAddressPrefix: '*'
          destinationAddressPrefix: '*'
          access: 'Allow'
          priority: 300
          direction: 'Inbound'
          sourcePortRanges: []
          destinationPortRanges: []
          sourceAddressPrefixes: []
          destinationAddressPrefixes: []
        }
      }
    ]
  }
}

resource vnet 'Microsoft.Network/virtualNetworks@2020-11-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: addressPrefix
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetPrefix
          networkSecurityGroup: {
            id: nsg.id
          }
        }
      }
    ]
  }
}

resource publicIP 'Microsoft.Network/publicIpAddresses@2019-02-01' = {
  name: publicIpAddressName
  location: location
  properties: {
    publicIPAllocationMethod: 'Dynamic'
  }
  sku: {
    name: 'Basic'
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2021-03-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: 'Standard_DS1_v2'
    }
    storageProfile: {
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
      }
      imageReference: {
        publisher: 'MicrosoftWindowsServer'
        offer: 'WindowsServer'
        sku: '2019-Datacenter'
        version: 'latest'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
        }
      ]
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPassword
      windowsConfiguration: {
        enableAutomaticUpdates: false
        provisionVMAgent: true
        patchSettings: {
          enableHotpatching: false
          patchMode: 'Manual'
        }
      }
    }
    diagnosticsProfile: {
      bootDiagnostics: {
        enabled: true
      }
    }
  }
}

resource extDSC 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = {
  parent: vm
  name: 'Microsoft.Powershell.DSC'
  location: 'westeurope'
  properties: {
    autoUpgradeMinorVersion: true
    publisher: 'Microsoft.Powershell'
    type: 'DSC'
    typeHandlerVersion: '2.83'
    settings: {
      ModulesUrl: '<PUT YOUR SAS TOKEN HERE AND USE ${environment().suffixes.storage}>'
      ConfigurationFunction: 'MyDSC1.ps1\\InstallApp'
      WmfVersion: 'latest'
      Privacy: {
        DataCollection: 'Enable'
      }
    }
    protectedSettings: {}
  }
}

The above script is not production code, and leaves doors open for hackers, so do not use that in production, it is here just for testing purposes and keeping things as simple as possible. Save the code above as vm.bicep and run the following commands to deploy it:

az login

az group create --name myRG --location "West Europe" 

az deployment group create --resource-group myRG --template-file vm.bicep

Now you can go to Azure portal, connect to the machine with RDP, and see the 7Zip installed on the Start Menu. Not too complicated, when you just find all the pieces needed to make it work.

Scale Set extensions

If you are wondering how to do the same with Virtual Machine Scale Set, wonder no more! You can use the resource definition for the extension from the script above, you just need to move it to ExtensionProfile under the scale set virtualMachineProfile. You would define the extension as extension profile to the scale set, like this:

      extensionProfile: {
        extensions: [
          {
            name: 'Microsoft.Powershell.DSC'
            properties: {
              autoUpgradeMinorVersion: true
              publisher: 'Microsoft.Powershell'
              type: 'DSC'
              typeHandlerVersion: '2.83'
              settings: {
                ModulesUrl: '<PUT YOUR SAS TOKEN HERE AND USE ${environment().suffixes.storage}>' 
                ConfigurationFunction: 'MyDSC1.ps1\\InstallApp'
                Properties: ''
                WmfVersion: 'latest'
                Privacy: {
                  DataCollection: 'Enable'
                }
              }
            }
          }          
        ]
      }

And if you are confused, how it would actually sit in the whole template, fear not, I have a full deployable scale set MVP (ports open to internet, so you can RDP to it, so not production ready!) here for you to try (do the az group create & az deployment using this file as parameter):

param adminUsername string = 'azureAdmin'
@secure()
param adminPassword string
param vmssName string = 'MyScaleSet'
param location string = resourceGroup().location
param windowsOSVersion string = '2019-Datacenter'

var osType = {
  publisher: 'MicrosoftWindowsServer'
  offer: 'WindowsServer'
  sku: windowsOSVersion
  version: 'latest'
}
var addressPrefix = '10.0.0.0/25'
var subnetPrefix = '10.0.0.0/26'
var virtualNetworkName = 'myvnet'
var publicIPAddressName = 'mypip'
var subnetName = 'mysubnet'
var loadBalancerName = 'mylb'
var natPoolName = 'mynatpool'
var bePoolName = 'mybepool'
var natStartPort = 50000
var natEndPort = 50119
var natBackendPort = 3389
var nicname = 'mynic'
var ipConfigName = 'myipconfig'
var nsgName='mynsg'

resource vnet 'Microsoft.Network/virtualNetworks@2020-11-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefix
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetPrefix
          delegations: []
          privateEndpointNetworkPolicies: 'Enabled'
          privateLinkServiceNetworkPolicies: 'Enabled'
        }
      }
    ]
    virtualNetworkPeerings: []
    enableDdosProtection: false
  }
  dependsOn: [
    loadBalancer
    nsg
    publicIP
  ]
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2020-11-01' = {
  name: nsgName
  location: 'westeurope'
  properties: {
    securityRules: [
      {
        name: 'RDP'
        properties: {
          protocol: 'Tcp'
          sourcePortRange: '*'
          destinationPortRange: '3389'
          sourceAddressPrefix: '*'
          destinationAddressPrefix: '*'
          access: 'Allow'
          priority: 300
          direction: 'Inbound'
          sourcePortRanges: []
          destinationPortRanges: []
          sourceAddressPrefixes: []
          destinationAddressPrefixes: []
        }
      }
    ]
  }
}

resource nsgRule 'Microsoft.Network/networkSecurityGroups/securityRules@2020-11-01' = {
  parent: nsg
  name: 'RDP'
  properties: {
    protocol: 'Tcp'
    sourcePortRange: '*'
    destinationPortRange: '3389'
    sourceAddressPrefix: '*'
    destinationAddressPrefix: '*'
    access: 'Allow'
    priority: 300
    direction: 'Inbound'
    sourcePortRanges: []
    destinationPortRanges: []
    sourceAddressPrefixes: []
    destinationAddressPrefixes: []
  }
}

resource publicIP 'Microsoft.Network/publicIPAddresses@2020-06-01' = {
  name: publicIPAddressName
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAddressVersion: 'IPv4'
    publicIPAllocationMethod: 'Static'
  }
}

resource loadBalancer 'Microsoft.Network/loadBalancers@2020-06-01' = {
  name: loadBalancerName
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    frontendIPConfigurations: [
      {
        name: 'LoadBalancerFrontEnd'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: publicIP.id
          }
        }
      }
    ]
    backendAddressPools: [
      {
        name: bePoolName
      }
    ]
    inboundNatPools: [
      {
        name: natPoolName
        properties: {
          frontendIPConfiguration: {
            id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', loadBalancerName, 'LoadBalancerFrontEnd')
          }
          protocol: 'Tcp'
          enableFloatingIP: false
          enableTcpReset: false
          frontendPortRangeStart: natStartPort
          frontendPortRangeEnd: natEndPort
          backendPort: natBackendPort
        }
      }
    ]
    probes: [
      {
        name: 'tcpProbe'
        properties: {
          protocol: 'Tcp'
          port: 80
          intervalInSeconds: 5
          numberOfProbes: 2
        }
      }
    ]    
    loadBalancingRules: [
      {
        name: 'LBRule'
        properties: {
          frontendIPConfiguration: {
            id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', loadBalancerName, 'LoadBalancerFrontEnd')
          }
          backendAddressPool: {
            id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadBalancerName, '${bePoolName}')
          }
          protocol: 'Tcp'
          frontendPort: 80
          backendPort: 80
          enableFloatingIP: false
          idleTimeoutInMinutes: 5
          enableTcpReset: false
          disableOutboundSnat: false
          loadDistribution: 'Default'
          probe: {
            id: resourceId('Microsoft.Network/loadBalancers/probes', loadBalancerName, 'tcpProbe')
          }
        }
      }
    ]
  }
}

// VM Scale set
resource vmss 'Microsoft.Compute/virtualMachineScaleSets@2020-06-01' = {
  name: vmssName
  location: location
  sku: {
    name: 'Standard_DS1_v2'
    tier: 'Standard'
    capacity: 2
  }
  properties: {
    overprovision: true
    upgradePolicy: {
      mode: 'Manual'
      automaticOSUpgradePolicy: {
        enableAutomaticOSUpgrade: false
      }
    }
    virtualMachineProfile: {
      storageProfile: {
        osDisk: {
          createOption: 'FromImage'
          caching: 'ReadWrite'
        }
        imageReference: osType
      }
      osProfile: {
        computerNamePrefix: 'my'
        adminUsername: adminUsername
        adminPassword: adminPassword
      }
      networkProfile: {
        networkInterfaceConfigurations: [
          {
            name: nicname
            properties: {
              primary: true
              enableAcceleratedNetworking: true
              networkSecurityGroup: {
                id: nsg.id
              }
              dnsSettings: {
                dnsServers: []
              }
              enableIPForwarding: false              
              ipConfigurations: [
                {
                  name: ipConfigName
                  properties: {
                    publicIPAddressConfiguration: {
                      name: 'publicIp-my-vnet-nic01'
                      properties: {
                        idleTimeoutInMinutes: 15
                        ipTags: []
                        publicIPAddressVersion: 'IPv4'
                      }
                    }
                    primary: true
                    subnet: {
                      id: '${vnet.id}/subnets/${subnetName}'
                    }
                    loadBalancerBackendAddressPools: [ 
                      {
                          id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Network/loadBalancers/${loadBalancerName}/backendAddressPools/${bePoolName}'
                      }
                    ]
                    loadBalancerInboundNatPools: [ 
                      {
                        id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Network/loadBalancers/${loadBalancerName}/inboundNatPools/${natPoolName}'
                      }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
      extensionProfile: {
        extensions: [
          {
            name: 'Microsoft.Powershell.DSC'
            properties: {
              autoUpgradeMinorVersion: true
              publisher: 'Microsoft.Powershell'
              type: 'DSC'
              typeHandlerVersion: '2.83'
              settings: {
                ModulesUrl: '<YOUR SAS TOKEN HERE>' 
                ConfigurationFunction: 'MyDSC1.ps1\\InstallApp'
                Properties: ''
                WmfVersion: 'latest'
                Privacy: {
                  DataCollection: 'Enable'
                }
              }
            }
          }          
        ]
      }
    }
  }
}
Posted in Azure | Tagged , , , , , , , | Leave a comment

Connect Arduino to Raspberry via USB to read serial data

Okay, first of all, this thing has been made way too complicated in the official samples, where they require you to get USB to TTL cables or similar, when all you really need is the product id and vendor id, which you can easily check and hard code on your project.

So, if you are doing some cool IOT project, and have Arduino reading sensors and writing to serial port the results, this is what you need on the Raspberry side to make the magic work:

You need to include two namespaces

using Windows.Devices.SerialCommunication;
using Windows.Devices.Enumeration;

What I did next was based on hint from article by Bereket Godebo, INTERFACING ARDUINO IN THE UNIVERSAL WINDOWS PLATFORM , which is in C++/CX, but I found out that when you connect the Arduino first to normal PC, go to the device manager and from the ports section you open Arduino, go to Details -section, select Hardware Id from the Property combo, and there you have a string which looks something like USB\\VID_2341&PID_0043… where you take the vid and pid and input them as hexa values to the following code:

var aqs = SerialDevice.GetDeviceSelectorFromUsbVidPid(0x2341, 0x0043);
var devices = await DeviceInformation.FindAllAsync(aqs);

First parameter is the vendor id, second is product id. The ids used above are for Arduino Uno, so if you have one, that code should work as it is.

Now the rest of the code needed to read the serial is only as follows:

// member variables
private SerialDevice serialPort;
private DataReader reader;
private CancellationTokenSource ReadCancellationTokenSource;

Put this in your method, where you want to do the reading, as it will find the the arduino, and create serial connection to it, and create loop to call for reading the input:

var aqs = SerialDevice.GetDeviceSelectorFromUsbVidPid(0x2341, 0x0043);
var devices = await DeviceInformation.FindAllAsync(aqs);
            
if (devices.Count > 0)
{
    Debug.WriteLine("Serial devices found...");
    DeviceInformation deviceInfo = devices[0];
    serialPort = await SerialDevice.FromIdAsync(deviceInfo.Id);
    serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
    serialPort.BaudRate = 9600;
    serialPort.Parity = SerialParity.None;
    serialPort.DataBits = 8;
    serialPort.StopBits = SerialStopBitCount.One;

    try
    {
        if (serialPort != null)
        {
            reader = new DataReader(serialPort.InputStream);
            Debug.WriteLine("Reading serial port COM");

            // Read the serial input in loop
            while (true)
            {
                await ReadAsync(ReadCancellationTokenSource.Token);
            }
        }
    }
    catch (TaskCanceledException tex)
    {
        Debug.WriteLine("Reading was cancelled");

        serialPort.Dispose();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
    }
    finally
    {
        if (reader != null)
        {
            reader.DetachStream();
            reader = null;
        }
    }
}

The actual reading of the serial port happens in this method:

private async Task ReadAsync(CancellationToken cancellationToken)
{
    Task<UInt32> loadAsyncTask;
    uint ReadBufferLength = 1024;

    // Cancel in case it was requested 
    cancellationToken.ThrowIfCancellationRequested();
    reader.InputStreamOptions = InputStreamOptions.Partial;
    loadAsyncTask = reader.LoadAsync(ReadBufferLength).AsTask(cancellationToken);

    // Launch the task and wait 
    UInt32 bytesRead = await loadAsyncTask;
    if (bytesRead > 0)
    {
        string txt = reader.ReadString(bytesRead);
        Debug.WriteLine(txt);
    }
} 

That’s all needed to connect Raspberry to read Arduino through normal USB cable plugged into Arduino, connected to Raspberry.

Posted in Uncategorized | Leave a comment

Using Cognitive Services with Xamarin Forms

I did a talk at DotNext conference about this and thought I share the demo here as well. In this demo we’ll create simple Xamarin Forms app for Android and UWP, camera and add the Cognitive Services Emotion and Computer Vision APIs to analyze the photo taken by the camera.

Setup

It all starts with File, New Project… and selecting from under Cross Platform (in Visual Studio 2015) project type Blank App (Xamarin.Forms Portable). Once you have created the project, you can remove the other projects, but leaveyourapp, yourapp.droid and yourapp.UWP for this demo.

Next you’ll need to add three Nuget -packages by selecting the Solution from the Solution Explorer and right clicking it and Manage Nuget packages for solution…. Go to Browse tab, and type Microsoft.ProjectOxford and hit enter.

Now you are presented with a list of all Cognitive Services Nuget packages, but you need to select only two from the list. First select Microsoft.ProjectOxford.Emotion and from the right side panel, click only the main project, not the yourapp.Droid or yourapp.UWP projects. Now select from the list Microsoft.ProjectOxford.Vision and repeat the previous selection.

We need to have the camera access, so we’ll add one more Nuget package, Xam.Plugin.Media (type that to Browse search box like previous) but this time you need to install this to all projects, yourapp, yourapp.droid and yourapp.UWP.

We need to do one more step inside the Nuget package manager. Go to updates tab, empty the field where you typed the package names and find the Xamarin.Forms from the list and update it to the latest version (if you don’t empty the search box, you will not see anything on updates list).

Now we need to ensure that security requirements are in place, you need to open Package.appxmanifest in the yourapp.UWP project and from the Capabilities -tab select Pictures Library and Webcam and save it. There’s one last step we need to do for configuration before we’re ready start coding, and that is to include the UWP version to build (it’s not by default, which is weird!). On the toolbar select the Debug dropdown and Configuration Manager from the list. Make sure that Build and Deploy options are selected for yourapp.UWP project.

Next step is to get the keys to use to authenticate your app with Cognitive Services. Open the link Cognitive services subscription and login with your Microsoft account to request keys for Computer Vision and Emotion.

Code

We’ll implement a simple UI with a button to snap a photo and send it to Cognitive services and image to show the image from the camera. We’ll implement all this code within the app.cs so it will run on all platforms without need for any platform specific code. For the first step we’ll add the required using statements to the beginning of the file:

using Microsoft.ProjectOxford.Emotion;
using Microsoft.ProjectOxford.Vision;
using Plugin.Media;
using System.Diagnostics;

Next we’ll add the following member variables inside your app -class:

string key = "YOUR_EMOTION_API_KEY", key2 = "YOUR_COMPUTER_VISION_KEY";

Now copy your new key values inside to those variables from the webpage and you’re all set to start calling Cognitive services.

Then we need to add the following member variables under the key -variable that you just added in previous step:

Image image = new Image();
StackLayout layout;

The image is for showing the camera photo and layout is the forms base layout control, which will hold all the controls for the app.

Inside the app.cs go to the public App() -constructor and delete everything else but leave only the

MainPage = new NavigationPage(content);

Now we add the UI for the app just above that Mainpage -line like this:

// The root page of your application
layout = new StackLayout
{
    BackgroundColor = Xamarin.Forms.Color.Gray
};
var button = new Button
{
    HorizontalOptions = LayoutOptions.Center,
    Text = "photo"
};
button.Clicked += Button_Clicked;
layout.Children.Add(button);
layout.Children.Add(image);
var content = new ContentPage
{
    Content = layout
};

We set the background color ourselves as Android is using by default dark theme and UWP light theme, so the text will be visible. Next we create a button in the middle of the screen and add event handler for it, and lastly we add image and button controls to the screen layout.

Now we are ready to take the photo, and call the cognitive services with our photo. We’ll add the button click handler where we do all this by adding this method:

private async void Button_Clicked(object sender, EventArgs e)
{
    // Camera magic
    await CrossMedia.Current.Initialize();

    if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
    {
        return;
    }

var mediaOptions = new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
    CompressionQuality = 100,
    PhotoSize = PhotoSize.Full,
    Directory = "Faces",
    Name = $"{DateTime.UtcNow}.jpg"
};
var file = await CrossMedia.Current.TakePhotoAsync(mediaOptions);
}

The first line is initializing the camera function, then we’ll check there is a camera on the device which can take a photo before continuing. The camera options are set so it knows where to save the images and using what filename (we use current date+time). Last line opens the platform specific camera launcher and returns the file from there which includes the photo which the user took.

Now we’re ready to send this image for analysis on the cloud. Add the following code in the end of Button_Clicked -method:

if (file != null)
{
    image.Source = ImageSource.FromFile(file.Path);
    var emotionClient = new EmotionServiceClient(key);
    var imageStream = file.GetStream();

    // Emotion API
    var emotion = await emotionClient.RecognizeAsync(imageStream);
    Debug.WriteLine($"Anger: {emotion[0].Scores.Anger : #0.##%}");
    Debug.WriteLine($"Contempt: {emotion[0].Scores.Contempt: #0.##%}");
    Debug.WriteLine($"Disgust: {emotion[0].Scores.Disgust: #0.##%}");
    Debug.WriteLine($"Fear: {emotion[0].Scores.Fear: #0.##%}");
    Debug.WriteLine($"Happiness: {emotion[0].Scores.Happiness: #0.##%}");
    Debug.WriteLine($"Neutral: {emotion[0].Scores.Neutral: #0.##%}");
    Debug.WriteLine($"Sadness: {emotion[0].Scores.Sadness: #0.##%}");
    Debug.WriteLine($"Surprise: {emotion[0].Scores.Surprise: #0.##%}");

    // Computer Vision
    var imageStream2 = file.GetStream();
    var visionClient = new VisionServiceClient(key2);
    VisualFeature[] visualFeats = new VisualFeature[]
    {
        VisualFeature.Description,
        VisualFeature.Faces
    };

    var analysis = await visionClient.AnalyzeImageAsync(imageStream2, visualFeats);
    Debug.WriteLine($"{analysis.Faces[0].Gender}, age {analysis.Faces[0].Age}");
    Debug.WriteLine(analysis.Description.Captions[0].Text);
    foreach (var tag in analysis.Description.Tags)
        Debug.WriteLine(tag);
}

On the code above we’ll set first the image control to show the image we captured. For the next step we use the key we requested earlier to open connection to the Emotion api service in the cloud, and then get stream of the image which we send to the API for analysis. Next you should check in real code that you actually got results, but here we assume it worked and just dump output to the console, formatting the results in percentage numbers.

In the Computer Vision -section of the code we need to get another stream as the previous call closed the earlier stream, and then set the visual features which we want to analyze from the picture. There are more options available, but more you ask, the slower it is, so we just want to get Description of the image and faces of the image this time. We call the AnalyzeImageSync with the file stream and visual feature request and again assume we get results, and dump them to the output console inside Visual Studio.

That was pretty easy to implement, but was a bit tricky for a beginner so I thought this would be useful guide for someone. Hope you enjoyed and let me know if you did or didn’t!

Posted in Uncategorized | Leave a comment

Connect SensorTag CC2650 to Azure IOT Hub

I needed to create a demo showing the most simple way to get some sensor data to IOT Hub and I thought sharing the code might be helpful to someone else as well. This app is divided to two parts, first part handling the sensor and second part connecting to IOT Hub.

First we start by creating a new empty UWP app, you can give whatever name you want to it. Because Texas Instrument SensorTag is a bluetooth device, we’ll first initialize the bluetooth connection bits (please note that you need to manually pair the Sensortag in the Windows 10 settings, we’re only dealing here with the code which handles paired devices).

First open the Package.appxmanifest, and in Capabilities tab tick the Bluetooth. Open your Mainpage.xaml.cs file and add the following namespaces:

using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Storage.Streams;
using Windows.ApplicationModel.Core

We will be needing couple of class members, so let’s add this now:

private BluetoothLEDevice tagDevice;
private GattDeviceService gattDevice;

Next we add the code to find the paired SensorTag device. I placed this code in Mainpage_Loaded override, but you can find more suitable place for it in your code:

foreach (var info in await DeviceInformation.FindAllAsync(BluetoothLEDevice.GetDeviceSelector()))
{
    BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromIdAsync(info.Id);
    if (bleDevice.Name.ToLower().Contains("sensortag"))
    {
        tagDevice = bleDevice;
        foreach (var serv in tagDevice.GattServices)
        {
            if (serv.Uuid.ToString() == "f000aa00-0451-4000-b000-000000000000")
            {
                gattDevice = serv;
                break;
            }
        }
        break;
    }
}

The code above first goes through all the BLE devices connected, and then checks for each of them if they have “sensortag” somewhere in their name. This probably finds also first gen sensortags, but you can always make it more specific if needed. After that we’re looking for a certain service with GattService id, which in this case is id for temperature sensor. Sensortag supports other ids/sensors as well, so you can always add more servies, and make a list of the gattDevice instead.

Next step is to start reading the Sensortag itself, and it can be done with the following code:

if (tagDevice != null && gattDevice != null)
{
    // Test connection
    Guid configuration = Guid.Parse("f000aa02-0451-4000-b000-000000000000"); // temperature profile
    var configurationCharacteristics = gattDevice.GetCharacteristics(configuration).First();

    GattCommunicationStatus status = await configurationCharacteristics.WriteValueAsync((new byte[] { 1 }).AsBuffer());

    if (status != GattCommunicationStatus.Success)
    {
        Debug.WriteLine("Connection failed, ensure the SensorTag is powered on");
    }
    else
    {
        // Setup the data read
        Guid data = Guid.Parse("f000aa01-0451-4000-b000-000000000000"); // temperature data
        GattCharacteristic dataCharacteristic = gattDevice.GetCharacteristics(data).First();
        if (dataCharacteristic.CharacteristicProperties == 
            (GattCharacteristicProperties.Read | 
            GattCharacteristicProperties.Notify))
        {
            dataCharacteristic.ValueChanged += this.dataValueChanged;
            status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                GattClientCharacteristicConfigurationDescriptorValue.Notify);

            if (status != GattCommunicationStatus.Success)
            {
                Debug.WriteLine("Sensor access denied");
            }
        }
    }
}

The codeblock above first looks for the temperature service from the gatt device, and tests if the connection can be established and if everything is ok and we can read and get a notify for the data change, we’ll setup a callback function for temperature data. Yet again you can setup multiple callbacks for different sensors by using the GetCharacteristics with different sensor data guids. All that is left is to do the callback method now, which is like this:

private async void dataValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
    GattReadResult readResult = await sender.ReadValueAsync(BluetoothCacheMode.Uncached);
    Color textColor;

    if (readResult.Status == GattCommunicationStatus.Unreachable)
    {
        Debug.WriteLine("Connection lost");
    }

    // Read the sensor value
    var result = new byte[readResult.Value.Length];
    DataReader.FromBuffer(readResult.Value).ReadBytes(result);

    // Extract ambiant temperature
    double ambTemp = BitConverter.ToUInt16(result, 2) / 128.0;

    string data = $"{ambTemp}";

    // Code to send data to IOT Hub
    //TagSensor sensor = new TagSensor() {Temperature = ambTemp};
    //await SendEvent(sensor);

    await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
            () =>
            {
                try
                {
                    // This is needed in case you want to update UI with sensor data, temperatureText is a TextBlock
                    //temperatureText.Text = data;
                }
                catch (Exception ex) { }
            });
            
    Debug.WriteLine(data);

}

At this point we are able to read the SensorTag temperature values, and get callback for the changes. Next we’ll add the code required to send this to IOT Hub.

First you should create new IOT Hub in Azure portal, and take note of the Connection String – Primary key (can be found in Shared Access Policies, iothubowner), and copy the value to the connectionString -member variables of the MainPage.xaml.cs and the host name to connectionString2:

// IOT Hub
static RegistryManager registryManager;
static string connectionString = "HostName=XXXXX.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=XXXXXXXXXXXXXXXXXXXXXXXXX";
static string connectionString2 = "XXXXX.azure-devices.net";
static string deviceKey;
private DeviceClient deviceClient;

Add the following Nuget packages to your solution:
– Microsoft.Azure.Devices
– Microsoft.Azure.Devices.Client
and in case it didn’t add automatically, Newtonsoft.JSon as well.

Next you should add the namespaces as well:

using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices;
using Microsoft.Azure.Devices.Common.Exceptions;
using System.Runtime.Serialization.Json;
using System.Threading.Tasks;

Use the latest stable versions of them.

For the device to be able to connect to the IOT Hub you first need to setup the device in the device registry. That can be done with the following code:

private async Task AddDeviceAsync()
{
    registryManager = RegistryManager.CreateFromConnectionString(connectionString);

    string deviceId = "TiTag1";
    Device device = null;
    try
    {
        device = await registryManager.AddDeviceAsync(new Device(deviceId));
    }
    catch (DeviceAlreadyExistsException)
    {
        device = await registryManager.GetDeviceAsync(deviceId);
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
    }
    if (device != null)
        deviceKey = device.Authentication.SymmetricKey.PrimaryKey;

}

The device id can be anything you want to give it, in this demo I just simply call it TiTag1.

Next add the call to the method in the beginning of the Mainpage_Loaded (remember to change it to private async void Mainpage_Loaded to be able to call async code inside it):

// Create the IoT Hub Device Client instance
await AddDeviceAsync();
deviceClient = DeviceClient.Create(connectionString2, new DeviceAuthenticationWithRegistrySymmetricKey("TiTag1", deviceKey));

Now is a good time to add a class to represent the sensor, where you can add the other sensors later if you like. Add this before Mainpage class:

public class TagSensor
{
    public TagSensor()
    {
        Time = DateTime.Now;
    }

    public DateTime Time;
    public double Temperature;
}

Next we’ll need to serialize the date to Json, and send to IOT Hub, which can be done like this:

private async Task SendEvent(TagSensor sensor)
{
    var serializer = new DataContractJsonSerializer(typeof(TagSensor));
    var stream = new MemoryStream();
    serializer.WriteObject(stream, sensor);
    string json = Encoding.UTF8.GetString(stream.ToArray(), 0, (int)stream.Length);

    var eventMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(json));
    await deviceClient.SendEventAsync(eventMessage);
}

Last piece is to remove the comments in dataValueChanged -method from TiTag Sensor… and async SendEvent -lines and you should be good to go. Please let me know in the comments if you are having issues, always happy to help!

Posted in Uncategorized | Leave a comment

Add Windows Hello -sign in to your app

With Windows 10 you have option to use Biometric methods to login, but it looks like there is quite little documentation how to implement it. Another thing is that people confuse Windows Hello to authentication framework which it’s not, even you can use it with such to authenticate. So this article shows you how to implement the login functionality to your UWP app using Microsoft Passport and Windows Hello.

It’s important to understand that this will use what ever is available, it could be iris recognizion on for example on Lumia 950 or fingerprint on your Thinkpad or just pin code if the machine doesn’t have any biometric sensors. The code itself is actually very simple, and this is all you would need:

Add the required reference to app.xaml.cs

using Windows.Security.Credentials;
using Windows.Security.Cryptography;

After that we can implement the login in App.xaml.cs and you can do this in many different ways, but I have a static member variable here:

private static bool authorized = false;

Now all is left is to do the actual login, which you can copy/paste to your code (beginning of OnLaunched -method):

// Do we have capability to provide credentials from the device
if (await KeyCredentialManager.IsSupportedAsync())
{
    // Get credentials for current user and app
    KeyCredentialRetrievalResult result = await KeyCredentialManager.OpenAsync("MyAppCredentials");
    if (result.Credential != null)
    {
        KeyCredentialOperationResult signResult =
            await
                result.Credential.RequestSignAsync(CryptographicBuffer.ConvertStringToBinary("LoginAuth",
                    BinaryStringEncoding.Utf8));
        if (signResult.Status == KeyCredentialStatus.Success)
        {
            authorized = true;
        }
    }
    // No previous saved credentials found
    else
    {
        KeyCredentialRetrievalResult creationResult =
            await
                KeyCredentialManager.RequestCreateAsync("MyAppCredentials",
                    KeyCredentialCreationOption.ReplaceExisting);
        if (creationResult.Status == KeyCredentialStatus.Success)
        {
            authorized = true;
        }
    }
}

When you check IsSupportedAsync you need to handle the situation that the device is not capable to provide this service, and you have to fallback to something else, such as Facebook or Twitter authentication. OpenAsync will check it there are saved credentials per app and user, and use those if can be found with RequestSignAsync. If there were no previous credentials for the app for current user, let’s create one. That’s all there is to it, very confusing topic but it is actually surprisingly easy to use. Hope this helps you!

Posted in Uncategorized | Leave a comment

Figuring out your if your app is run on Phone, Tablet or Desktop

One would think that it’s easy to figure out the answer to header above. If you’ve ever tried that, I think you most likely have become extremely frustrated when you realized that there are just too many combinations and it becomes quite impossible to make a difference between the devices with the provided APIs. For example Surface is a tablet running Desktop SKU while under 8″ screen tablets run Mobile SKU which is same SKU which is on the phones. For IoT there’s IoT Core and IoT Enterprise, which is actually Windows 10 Enterprise with additional IoT license.

In general you should always design for Universal which adapts to available screen space and hardware/capabilities but sometimes you just need to know (for example for analytic reasons) and that’s why I have created this little snippet. It should cover the most cases, but do let me know if the logic fails to identify something and I try to include that as well.

I threw in my best guess for Continuum and Iot as well, after some Twitter discussions with @dotMorten, @ScottIsAFool, @gcaughey and @mahoekst

public enum Device
{
    Phone,
    Tablet,
    Desktop,
    Xbox,
    Iot,
    Continuum
};
...
if (Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Mobile")
{
    if (ApiInformation.IsApiContractPresent("Windows.Phone.PhoneContract", 1))
    {
        Windows.Devices.Input.KeyboardCapabilities keyboard = new Windows.Devices.Input.KeyboardCapabilities();
        if (keyboard.KeyboardPresent > 0)
        {
            runningDevice = Device.Continuum;
        }
        else
        {
            runningDevice = Device.Phone;
        }
    }
    else
    {
        runningDevice = Device.Tablet;
    }
}
else if (Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Desktop")
{
    Windows.Devices.Input.KeyboardCapabilities keyboard = new Windows.Devices.Input.KeyboardCapabilities();
    if (keyboard.KeyboardPresent > 0)
    {
        runningDevice = Device.Desktop;
    }
    else
    {
        runningDevice = Device.Tablet;
    }
}
else if (Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Xbox")
{
    runningDevice = Device.Xbox;
}
else if (Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.IoT")
{
    runningDevice = Device.Iot;
}

// Couldn't figure out, let's assume it's desktop (safest bet)
runningDevice = Device.Desktop;
Posted in Uncategorized | Leave a comment

How I do Hamburger menu on SplitView, quick and dirty

I wanted to do a nice hamburger menu on my app, but it seems that all the samples found are doing it in different way compared how the default apps on Windows 10 seem to implement it. I don’t want to have CompactInlay menu always visible on the side of the screen, but just the hamburger button which would open it when clicked. This is how I implemented mine, hope you can get something out of it:

First I created BoolToVisibilityConverter class, so I can later on bind the visibility of the menu to the hamburger button (right click on project, add new item, class):

public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return (bool)value ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

Then I open the MainPage.xaml and add the reference to the namespace in the page element after xmlns:local -line:

xmlns:utils="using:MyNameSpace.Utils"

where the MyNameSpace.Utils is the namespace where you created the BoolToVisibilityConverter -class in previous step.

After that, you would need to add in the resources the following:

<Page.Resources>
        <utils:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter" />
    </Page.Resources>

Now that we have all the prerequisities in place, we can build the page itself. This is the barebone structure for the main page with hamburger menu and splitview:

<Grid Background="#FFFF8000">
    <Grid.RowDefinitions>
        <RowDefinition Height="50"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <!--Menu bar on top-->
    <RelativePanel Grid.Row="0" >
        <ToggleButton x:Name="HamburgerButton" FontFamily="Segoe MDL2 Assets" Content="?" Width="50" Height="50" Background="Transparent" IsChecked="False"/>
        <TextBlock Text="My title" FontSize="32" Width="300" Height="40" Foreground="Black" RelativePanel.RightOf="HamburgerButton"/>
    </RelativePanel>
    <!--Hamburger menu-->
    <SplitView Grid.Row="1" x:Name="MySplitView" PanePlacement="Left" CompactPaneLength="50" OpenPaneLength="300" 
            IsPaneOpen="{Binding IsChecked, ElementName=HamburgerButton}" DisplayMode="Overlay" >
        <SplitView.Pane>
            <StackPanel Orientation="Vertical">
                <StackPanel x:Name="SettingsPanel" Orientation="Horizontal">
                    <Button x:Name="SettingsButton" FontFamily="Segoe MDL2 Assets" Content="?" Width="50" Height="50"/>
                    <TextBlock Text="Settings" FontSize="18" VerticalAlignment="Center" />
                </StackPanel>
                <StackPanel x:Name="AboutPanel" Orientation="Horizontal">
                    <Button x:Name="AboutButton" FontFamily="Segoe MDL2 Assets" Content="&#xE77B" Width="50" Height="50"/>
                    <TextBlock Text="About" FontSize="18" VerticalAlignment="Center" />
                </StackPanel>
            </StackPanel>
        </SplitView.Pane>
        <!--Main content-->
        <Grid Grid.Row="1">
            
        </Grid>
    </SplitView>
</Grid>

On the code above, the RelativePanel section is for the menu bar, where the button and title are. In the SplitView section there is the SplitView.Pane where the actual menu is built, each item consists of vertical StackPanelButton and TextBlock. Inside then there is the Grid where the actual page content can be put. That’s all required for simple hamburger menu, hope it’s helpful!

Posted in Uncategorized | Leave a comment

How to make a Windows Store game with C# and XAML, part 5

In this mini post we’ll be finally adding some sounds to our game. You will need to have some sound effects added to your Assets -folder. You need one for laser shooting and one for enemy hit sound. One place you could download some of these for free is Free game content resources. Add one with the name pew.mp3 under Assets (right click, add existing..) and one with the name pow.mp3

Windows 10 finally brings an easy way to add low latency sound to your universal app with managed classes, without need to do interop with DirectX. The main class is AudioGraph and it is good replacement for XAudio2.

We nee to edit GamePage.xaml.cs and add first three using statements, first of them is not needed for the sound but just to get some debug output in case something goes wrong with the initialization of the audio.

using Windows.Media.Audio;
using System.Diagnostics;
using Windows.Storage;
using Windows.Media.Render;

Now we need to add the member variables which will be used to play the audio, and store the audio files in the memory. AudioGraph class is for the audio playing, AudioDeviceOutputNode is the output device where the sound is played and the AudioFileInputNode has the sound file. These go as member variables to GamePage class, add them under the bool fireSuppressor:

private AudioGraph graph;
private AudioDeviceOutputNode deviceOutput;
private Dictionary<string, AudioFileInputNode> fileInputs = new Dictionary<string, AudioFileInputNode>();

In the Loaded we need to add the the following:

InitSound();

Next we need to add the method itself, which will create the audio pipeline, load the sounds and enable us to play them later on:

private async System.Threading.Tasks.Task InitSound()
{
    AudioGraphSettings settings = new AudioGraphSettings(AudioRenderCategory.Media);
    CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);

    if (result.Status == AudioGraphCreationStatus.Success)
    {
        graph = result.Graph;

        // Create the output device for audio playing
        CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await graph.CreateDeviceOutputNodeAsync();

        // Ensure there's audio output ready and available
        if (deviceOutputNodeResult.Status == AudioDeviceNodeCreationStatus.Success)
        {
            deviceOutput = deviceOutputNodeResult.DeviceOutputNode;
            graph.ResetAllNodes();

            await AddFileToSounds("ms-appx:///Assets/pew.mp3");
            await AddFileToSounds("ms-appx:///Assets/Pop.mp3");
            // here you could list all your sounds like row above

            graph.Start();
        }
    }
}

Add this method, which adds a given sample to memory for playing later on:

/// <summary>
/// Load and add resource sound file to memory dictionary for playing
/// </summary>
/// <returns></return>
private async System.Threading.Tasks.Task AddFileToSounds(string uri)
{
    var soundFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(uri));
    CreateAudioFileInputNodeResult fileInputResult = await graph.CreateFileInputNodeAsync(soundFile);

    if (AudioFileNodeCreationStatus.Success == fileInputResult.Status)
    {
        fileInputs.Add(soundFile.Name, fileInputResult.FileInputNode);
        fileInputResult.FileInputNode.Stop();
        fileInputResult.FileInputNode.AddOutgoingConnection(deviceOutput);
    }
}

That is all what is needed for setting up playing sounds. Next job for us is to hook the sound playing in the correct places in the code and play the actual sounds. Go to OnFire methos and add the following under bullets.Add(bullet); :

// Play the sound
var pew = fileInputs["pew.mp3"];
pew.Reset();
pew.Start();

The previous code is code to play the shooting sound, now we add the explosion sound for hitting enemy. Go to the HitTest and add under the enemies[i].Dead = true; the following code:

// Play the sound
var pop = fileInputs["Pop.mp3"];
pop.Reset();
pop.Start();

That’s all, now just compile and run the game, and enjoy your sound effects on the game! On the next blog post we’ll be talking about scaling the app on different screens properly, doing the Windows 10 adaptive screen magic.

Posted in Uncategorized | Leave a comment

How to make a Windows Store game with C# and XAML, part 4

After long pause, here’s the next edition of XAML gaming series. If you are serious game developer, you should check out some of the gaming frameworks, such as Unity or Cocos2D. You could also be interested in Win2D library if you prefer XNA type of programming. The idea of this series is to have fun and learn XAML/C# and Windows features at the same time.

In this episode we bring our project to Windows 10, and add Xbox 360/One controller support for our game. In next part we talk about adding sounds and some other fun things.

You will need to have Windows 10 and any edition of Visual Studio 2015 installed to go on with this project. First, make a backup of your game, and copy it somewhere safe before proceeding with this guide, just in case something goes haywire while converting the project as the following steps include irreversable deleting of files.

Conversion preparations

Start your project in Visual Studio 2015 and now that you have the backup safely in different folder, you can delete the MySpaceInvanders.WindowsPhone project from the solution. Next you select all the files in the Shared-project, and cut/paste them to the MySpaceInvanders.Windows project. Don’t forget to cut/paste also Assets and Common –folders and their content.

Open the App.xaml.cs file, and find all code which is inside #if WINDOWS_PHONE_APP -block and remove it. Remove also the line #ELSE (not the code inside the block) and #ENDIF line. Now you should have no conditional compilation inside the App.xaml.cs file. We handle the other files later.

There’s no AnyCPU project in Windows 10 by default, so we remove it from our solution. Open the Configuration Manager (the small arrow next to AnyCPU text), and choose Configuration Manager, and from Active Solution Platform dropdown choose Edit... Now select AnyCPU and click Remove and accept the removal. Now you can close the Configuration Manager. Select x86 configuration as active configuration.

Conversion

Unfortunately there’s no conversion tool for Windows Universal apps to Windows 10 build inside the Visual Studio, but luckily Andy Wigley and Jerry Nixon have provided such script for public availability.

Open https://github.com/Win10DevGuideMVA/ProjectUpgradeUtility in your browser, and download the zip file. Unpack these script files to your MySpaceInvanders.Windows folder, and open command prompt, navigating to that folder and type to command prompt:

cd c:\source\MySpaceInvanders\MySpaceInvanders.Windows

where the folder matches your local folder and press enter. You are ready to do the actual conversion, so just run the script by typing:

Run_Upgrade_to_uwp.bat

Your output should match something like the following screenshot (Two Trues and one Done without any errors):

You need to do couple of more steps before you can run your solution.

First open the Package.AppxManifest in code (right click the file in Solution Explorer and select View Code). You must change the text Square30x30Logo to Square44x44Logo. You need later on to upgrade the graphics to match this size as the old icon is too small and will give you error if you try to submit your game to the store.

If you have section called build:Metadata you should remove it completely. Now save the file, change the Build Configuration to ARM and X64 and repeat the previous steps for both configurations, saving the file every time and select lastly the x86 configuration back.

Now you need to remove one more #if WINDOWS_PHONE_APP conditional section from the code, this time from Startpage.xaml.cs where it checks the screen width and height. Your StartPage constructor should look like this:

public StartPage()
{
    this.InitializeComponent();

    ApplicationView view = ApplicationView.GetForCurrentView();

    bool isInFullScreenMode = view.IsFullScreenMode;

    if (!isInFullScreenMode)
    {
        view.TryEnterFullScreenMode();
    }

    Loaded += (sender, args) =>
    {
        CreateStar();
        Move.Completed += MoveStars;
        Move.Begin();
    };

    App.ScreenWidth = Window.Current.Bounds.Width;
    App.ScreenHeight = Window.Current.Bounds.Height;
}

After you’ve done that, you should be able to compile the project without errors and try it out.

Post-conversion

When you start the game, you notice that all the texts from the start screen are missing!

This is a great opportunity to learn a little bit of error resolving with Visual Studio 2015 and about a great new feature called Live Visual Tree. While your app is running, switch back to Visual Studio with alt+tab and go to DebugWindows and select Live Visual Tree. Expand the window so that you can see it clearly.

You should see tree nodes called StartPage and under it Grid and StartButton. There are two TextBlock items there as well. Now go on and select one of them. Right click it and select Show properties. You can see what values have been set to the control while it’s running and you are able to edit them while your game is running.

First thing to check is that what color we have set for the text foreground. There’s no Foreground set on Local -section, so it must be inherited from somewhere else. You can see those values in the Default -section.

Now that you found the Foreground, it is showing Black. Where did that come from? In Windows 10 there’s been one big change in application color schemas, the default theme has been set to light. In light theme the text is black and background is white. There’s easy fix for us – stop the debugger, go to the beginning of the xaml -file, and add the following property to the page element:

RequestTheme="Dark"

Add it also to the GamePage.xaml in the same place, for example as a last property just after mc:Ignorable=”d” -text but before the closing tag.

Now all the text should be visible again and you’re good to go.

You could add a small visual candy to the start screen at this point. In the XAML designer select the UNIVERSAL -text block and click Brush in the property window (bottom right of VS), and select the third box below it (Gradient Brush). Repeat the same for the INVANDERS -text. Change the Black in GradientStop color to DarkGrey for both textblocks in XAML of StartPage.xaml and run the app. Now there’s a small chrome effect in the texts.

Let’s get on with the show and continue the conversion. Open the Ship.xaml.cs and remove the #if WINDOWS_PHONE_APP section (and #else and #endif -lines as well). Now do the same in the GamePage.xaml.cs as well.

We need to change shipHorizontalPosition -variable initializer in GamePage.xaml.cs to following (remove readonly and adjust the size):

private double shipHorizontalPosition = App.ScreenHeight - 80;

Windows 10 has changed so that the Store apps can be windowed as well. Right now we don’t add the code to handle resizing (subject for later posts) but add request to run in full screen mode, which is logical for games. Open the StartPage.xaml.cs and add to the top:

using Windows.UI.ViewManagement; 

Next add the following code to constructor after the InitializeComponent:

ApplicationView view = ApplicationView.GetForCurrentView();

            bool isInFullScreenMode = view.IsFullScreenMode;

            if (!isInFullScreenMode)
            {
                view.TryEnterFullScreenMode();
            }

Now you are ready to run the game!

Xbox 360/One controller support

Now we can finally add some Windows 10 goodies to the project. I think adding the controller support is really fitting as it’s useful not only to games but good to learn because it will be relevant for apps as well to enable your app to be controller on Xbox One later on when the Store opens for submissions for it.

We need to take the controller in use in only one place and then use that object through our app. Open the App.xaml.cs file and add to the top:

using Windows.Gaming.Input;

and following member variable:

public static Gamepad GameController;

When we are starting our app, we need to listen to when the app gets the Xbox controller connection by adding handler for GamepadAdded event in constructor after this.Suspending += this.OnSuspending; -line:

Gamepad.GamepadAdded += Gamepad_GamepadAdded;

Add the following method just below the constructor:

private void Gamepad_GamepadAdded(object sender, Gamepad e)
        {
            GameController = Gamepad.Gamepads.First();
        }

By adding the code above, you have now access to Xbox controller through out your app. At this point your App.xaml.cs should look something like this:

using System;
using System.Linq;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using MySpaceInvanders.Common;
using Windows.Gaming.Input; // XBOX


namespace MySpaceInvanders
{
    /// <summary>
    /// Provides application-specific behavior to supplement the default Application class.
    /// </summary>
    public sealed partial class App : Application
    {
        public static double ScreenWidth { get; set; }
        public static double ScreenHeight { get; set; }
        public static int Highscore { get; set; }

        public static Gamepad GameController; // XBOX

        /// <summary>
        /// Initializes the singleton instance of the <see cref="App"/> class. This is the first line of authored code
        /// executed, and as such is the logical equivalent of main() or WinMain().
        /// </summary>
        public App()
        {
            this.InitializeComponent();
            this.Suspending += this.OnSuspending;
            Gamepad.GamepadAdded += Gamepad_GamepadAdded; // XBOX

        }
        private void Gamepad_GamepadAdded(object sender, Gamepad e)
        {
            GameController = Gamepad.Gamepads.First();
        }

        /// <summary>
        /// Invoked when the application is launched normally by the end user.  Other entry points
        /// will be used when the application is launched to open a specific file, to display
        /// search results, and so forth.
        /// </summary>
        /// <param name="e">Details about the launch request and process.</param>
        protected async override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                //Associate the frame with a SuspensionManager key                                
                SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

                // TODO: change this value to a cache size that is appropriate for your application
                rootFrame.CacheSize = 1;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // Restore the saved session state only when appropriate
                    try
                    {
                        await SuspensionManager.RestoreAsync();
                    }
                    catch (SuspensionManagerException)
                    {
                        // Something went wrong restoring state.
                        // Assume there is no state and continue
                    }
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter
                if (!rootFrame.Navigate(typeof(StartPage), e.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }

            // Ensure the current window is active
            Window.Current.Activate();
        }

        /// <summary>
        /// Invoked when application execution is being suspended.  Application state is saved
        /// without knowing whether the application will be terminated or resumed with the contents
        /// of memory still intact.
        /// </summary>
        private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            await SuspensionManager.SaveAsync();
            deferral.Complete();
        }
    }
}

Let’s start by adding ship movement and firing to the game. Open the Gamepage.xaml.cs -file, and add the following using statement:

using Windows.Gaming.Input;

For better gameplay experience, I have added also bit which prevents Xbox controller autofiring all the time when the trigger is pressed. If you don’t like this, you can just comment it out later on. Add the following member variable:

bool fireSuppressor; // XBOX, prevent autofire

We need a place where we can read the controller values enough often without affecting the gameplay. I have opted in using the MoveStars -method which is not necessary the elegant solution but works anyway nicely. Add the following code to the very beginning of MoveStars -method:

if (App.GameController != null)
            {
                GamepadReading reading = App.GameController.GetCurrentReading();
                
                // Use 0.4 so the stick don't have to be exactly to right or left, but is more forgiving
                if ((double)reading.RightThumbstickX < -0.4)
                {
                    MoveShip(-5);
                }
                else if ((double)reading.RightThumbstickX > 0.4)
                {
                    MoveShip(5);
                }

                if ((int)reading.RightTrigger > 0 && fireSuppressor == false)
                {
                    fireSuppressor = true;
                    OnFire(null, null);
                }
                else if((int)reading.RightTrigger == 0) // require depress of trigger before shoot another
                {
                    fireSuppressor = false;
                }
            }

In the code above we ask from the controller what are all the current readings in it. After that we check if the right thumb stick has been moved. I checked values above and below 0.4 so the user don’t have to move the stick exactly horizontally for the ship to move. After moving the ship I’m checking if the right trigger has been pressed, and it has been released between previous visit to this loop, forcing the player to pull the trigger for each shot.

Now you can test the game and you should be able to move the ship with right thumb stick and shoot with right trigger. I tested it with connecting Xbox One controller with the charging cable to the PC and it worked like a charm. The same code should run as it is with Xbox 360 controller as well.

One more addition is that we should be able to start the game with the controller as well. Go to the Startpage.xaml, change the StartButton Content to:”Start (A)”. Open the StartPage.xaml.cs and add the following using:

using Windows.Gaming.Input; // XBOX

After you’ve done that, find the MoveStars -method. Add the following code to the beginning of it:

           if (App.GameController != null)
            {
                GamepadReading reading = App.GameController.GetCurrentReading();

                if (reading.Buttons == GamepadButtons.A)
                {
                    OnStart(null, null);
                }
            }

Find the OnStart -method, and add the following to the beginning of it:

Move.Completed -= MoveStars;
            Move.Stop();

That’s it, now you can test your game and you should be able to start it by pressing the A-button on your Xbox controller, move your ship and shoot. I hope you are having fun with these and do leave comments what you would like to see next incorporated to the game (sounds are coming next, otherwise I’m open to suggestions).

Posted in Uncategorized | Leave a comment

How to make a Windows Store game with C# and XAML, part 3

Moving the ship
When we’re doing a universal app, we have to take in account that there are several possible ways for users to control the game. Phones and tablets have touch screens, but on desktop still most of us prefer to use keyboard to play games.

For doing the movements, we need to be aware of the screen dimensions. Add these member variables to App.xaml.cs class:

public static double ScreenWidth { get; set; }
public static double ScreenHeight { get; set; }
public static int Highscore { get; set; }

We set these values in StartPage.xaml.cs, at the end of the constructor, after the Loaded -lambda:

#if WINDOWS_PHONE_APP
            App.ScreenWidth = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Bounds.Width;
            App.ScreenHeight = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Bounds.Height;
#endif
#if WINDOWS_APP
            
            App.ScreenWidth = Window.Current.Bounds.Width;
            App.ScreenHeight = Window.Current.Bounds.Height;
#endif

Here we used #if statements to run different code depending on the platform we’re running, the code will be compiled to include just the platform specific code for each build for Phone and Windows.

Now we open the GamePage.xaml.cs and add the following member variables to keep track of the ship position:

private double shipPosition;
private readonly double shipHorizontalPosition = App.ScreenHeight - 50;

Now we’re ready to create the actual method to move the ship. We add first this to the top of the GamePage.xaml.cs:

using Windows.System;

And then this to the end of the GamePage.xaml.cs:

private void MoveShip(int amount)
{
    shipPosition += amount;
 
    // Let's make sure that the ship stays in the screen
    if (shipPosition > LayoutRoot.ActualWidth - 30)
    {
        shipPosition = LayoutRoot.ActualWidth - 30;
    }
    else if (shipPosition < 0)
    {
        shipPosition = 0;
    }
 
    Rocket.Margin = new Thickness(shipPosition, shipHorizontalPosition, 0, 0);
}

The method moves the ship the amount specified in the calling function (in pixels), and checks that it doesn’t go over the boundaries from left or right.

Next we’ll capture the keyboard in case the user is playing with desktop machine. Let’s create the event handler for key events. Open GamePage.xaml.cs constructor, and add the following after InitializeComponent():

Loaded += (sender, args) =>
{
    // Resize move controls to fit the area
    LeftCanvas.Width = LeftCanvas.Height = (LeftArea.ActualWidth / 2) - 10;
    RightCanvas.Width = RightCanvas.Height = (LeftArea.ActualWidth / 2) - 10;

    // Position the ship to the bottom center of the screen
    shipPosition = LayoutRoot.ActualWidth / 2;
    Rocket.Margin = new Thickness(shipPosition, shipHorizontalPosition, 0, 0);
 
    Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown;
};

Next we add the actual code to handle the key presses by adding the following method:

private void CoreWindow_KeyDown(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.KeyEventArgs args)
{
    switch (args.VirtualKey)
    {
        case VirtualKey.Left:
            MoveShip(-5);
            break;
        case VirtualKey.Right:
            MoveShip(5);
            break;
        case VirtualKey.Space:
            OnFire(null, null);
            break;
        default:
            break;
    }
}

Star field background
We had one type of particle engine on the start screen, but I think we need something of a more traditional kind to create sense of moving for the ship. First we need to add one using –statement to the top of the file:

using Windows.UI;

Then we need to add some more member variables for the starfield:

private const int StarCount = 200
private List<Dot> stars = new List<Dot>(StarCount);
private Random randomizer = new Random();

Here’s altered versions of CreateStar and MoveStars methods, which we add to the GamePage.xaml.cs:

void MoveStars(object sender, object e)
{
    if (stars.Count < StarCount)
    {
        CreateStar();
    }

    foreach (Dot star in stars)
    {
        Canvas.SetLeft(star.Shape, Canvas.GetLeft(star.Shape) + star.Velocity.X);
        Canvas.SetTop(star.Shape, Canvas.GetTop(star.Shape) + star.Velocity.Y);

        if (Canvas.GetTop(star.Shape) > LayoutRoot.ActualHeight)
        {
            int left = randomizer.Next(0, (int)LayoutRoot.ActualWidth);
            Canvas.SetLeft(star.Shape, left);
            Canvas.SetTop(star.Shape, 0);
        }
    }
    Move.Begin();
}
private void CreateStar()
{
    var star = new Dot()
    {
        Shape = new Ellipse() { Height = 2, Width = 2 },
        Velocity = new Point(0, randomizer.Next(1, 5))
    };

    int left = randomizer.Next(0, (int)LayoutRoot.ActualWidth);
    Canvas.SetLeft(star.Shape, left);
    Canvas.SetTop(star.Shape, 0);
    Canvas.SetZIndex(star.Shape, 1);
            
    // Set color
    byte c = (byte)randomizer.Next(10,255);
    star.Shape.Fill = new SolidColorBrush(Color.FromArgb(c, c, c, c));

    stars.Add(star);
    LayoutRoot.Children.Add(star.Shape);
}

Now we go to the constructor of the same class, and add inside the end of the Loaded lambda the following code:

// Starfield background
CreateStar();
Move.Completed += MoveStars;
Move.Begin();

Now if you run the game, you see how stars are falling in different speeds from the top of the screen, creating sense of depth and speed.

Adding enemies
What would a shoot’em up be without any enemies to shoot? Next we’ll add some enemies to the screen to get some action to the screen.

Let’s create a new UserControl, and call it Bobo by right clicking the Shared project, and Add, New Item, User Control. You could give it some nice bitmap images, but for this exercise I’m using XAML to draw the the creature. Open Bobo.xaml and copy/paste the following on top the XAML:

<UserControl
    x:Class="MySpaceInvanders.Bobo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Width="60" Height="60">
 
    <Canvas Width="60" Height="60">
        <Ellipse x:Name="InsideEllipse" Width="60" Height="60" Margin="0,0"/>
        <Polygon Stroke="Black" Fill="Green" Points="10,10,5,0,15,5"/>
        <Polygon Stroke="Black" Fill="Green" Points="45,5,55,0,50,10"/>
        <Ellipse Width="5" Height="5" Fill="Gold" Margin="20,20" />
        <Ellipse Width="5" Height="5" Fill="Gold" Margin="35,20" />
        <Ellipse Width="30" Height="8" Fill="HotPink" Margin="15,40" />
    </Canvas>
</UserControl>

Open the Bobo.xaml.cs and add the following member variables to the class:

public int AreaWidth { get; set; }
public Point Location { get; set; }
public bool Dead { get; set; }
public int Worth { get; set; } // amount of score for kill
public int Type; // 1 - green, 2 - blue, 3 - mega
private readonly Random randomizer = new Random();
public double Velocity;
private int direction;
private int directionCount = 0; // don't change direction on every loop

Add also the velocity and type randomizer to the constructor, just after the InitializeComponent –call:

Velocity = randomizer.Next(1, 3);
Type = randomizer.Next(1, 4);
if (Type == 3)
{
    Velocity = 4;
}
SetType();

Now we have three different types of enemies, and one of them moves faster than the others. For the enemy to move on the screen, we’ll add the Move method for it:

public void Move()
 {
     int move;
 
     // Randomize the move direction
     if (directionCount == 0)
     {
         direction = randomizer.Next(1, 3);
     }
     if (direction == 1)
     {
         move = -1;
     }
     else
     {
         move = 1;
     }
     directionCount++;
 
     // Change direction every 30 count
     if (directionCount > 30)
     {
         directionCount = 0;
     }
 
     // Check that the bobo doesn't go through the game area walls
     if (Location.X + direction < 0)
     {
         move = 0;
     }
     if (Location.X + direction > AreaWidth)
     {
         move = AreaWidth;
     }
 
     // Set the new location
     Location = new Point(Location.X + move, Location.Y + Velocity);
 }

We need to add also method to set the color according to the type of the enemy and give them unique kill score. Add the following method to Bobo.xaml.cs:

private void SetType()
{
    switch (Type)
    {
        case 1:
            SetFill(Color.FromArgb(0xFF, 0x00, 0xA2, 0x07), Color.FromArgb(0xFF, 0x3A, 0xFF, 0x00));
            Worth = 10;
            break;
        case 2:
            SetFill(Color.FromArgb(0xFF, 0x00, 0x00, 0xa0), Color.FromArgb(0xFF, 0x00, 0x0F, 0xff));
            Worth = 20;
            break;
        case 3:
            SetFill(Color.FromArgb(0xFF, 0xaf, 0x00, 0x00), Color.FromArgb(0xFF, 0xff, 0x0F, 0x00));
            Worth = 50;
            break;
    }
}

private void SetFill(Color start, Color end)
{
    var startGradient = new GradientStop();
    var endGradient = new GradientStop();
    startGradient.Color = start;
    startGradient.Offset = 1;
    endGradient.Color = end;
    var collection = new GradientStopCollection();
    collection.Add(startGradient);
    collection.Add(endGradient);

    InsideEllipse.Fill = new LinearGradientBrush(collection, 0);
}

Now we have a nasty looking foe for our ship to shoot at. Next we open the GamePage.xaml.cs and add again some prerequisites for our swarm of enemies. Add the following member variables:

private List<Bobo> enemies = new List<Bobo>();
private int maxEnemies = 20;
private DispatcherTimer timer = new DispatcherTimer();
private int Level { get; set; } // Player level 
private int Score { get; set; } // Game score

You can adjust the game difficulty by increasing maxEnemies at later level of the game for example.

We have everything set for our enemies to appear on the screen. We need to just add them to the GamePage.xaml.cs. First edit the Loaded –lambda on constructor to include the following at the bottom of it:

timer.Tick += TimerOnTick;
timer.Interval = new TimeSpan(0, 0, 0, 2);
timer.Start();

And then the Tick method to call:

/// <summary>
/// Create a new enemy if not max amount on the screen already
/// </summary>
/// <param name="sender"></param>
/// <param name="o"></param>
private void TimerOnTick(object sender, object o)
{
    if (enemies.Count < maxEnemies)
    {
        var enemy = new Bobo
        {
            AreaWidth = (int)LayoutRoot.ActualWidth,
            Location = new Point(randomizer.Next(0, (int)LayoutRoot.ActualWidth - 80), 0)
        };
        if (enemy.Type == 3)
        {
            // Make the red enemy smaller and more difficult to hit
            var scaleTransform = new ScaleTransform();
            scaleTransform.ScaleX = scaleTransform.ScaleX * 0.50;
            scaleTransform.ScaleY = scaleTransform.ScaleY * 0.50;
            enemy.RenderTransform = scaleTransform;
            enemy.Width = 30;
            enemy.Height = 30;
        }
        enemy.Velocity = enemy.Velocity * ((Level / (double)10) + 1);
        enemies.Add(enemy);
        Canvas.SetZIndex(enemy, 7);
        LayoutRoot.Children.Add(enemy);
    }
}

All this code will create different types of monsters to the screen, but they’re still sitting static on the top. That’s kind of boring, so let’s add the long awaited game loop to move them:

private void GameLoop(object sender, object e)
{
    if (goingRight)
        MoveShip(5);
    if (goingLeft)
        MoveShip(-5);
    // TODO - collision test

    // TODO - move bullets

    // Move enemies
    for (int i = 0; i < enemies.Count; i++)
    {
        if (enemies[i].Dead == false)
        {
            enemies[i].Move();
            enemies[i].Margin = new Thickness(enemies[i].Location.X, enemies[i].Location.Y, 0, 0);
        }
 
        if (enemies[i].Margin.Top > App.ScreenHeight || enemies[i].Dead)
        {
            LayoutRoot.Children.Remove(enemies[i]);
            enemies.Remove(enemies[i]);
        }
    }
}

Now we just have to make sure our game loop gets called, so we open the constructor, and add as very last line, after the Loading –lambda has closed, the following line:

CompositionTarget.Rendering += GameLoop;

Go ahead, try the project now, and you should see the star field moving, ship should respond to the keyboard and enemies should move randomly from top to down.

Open fire!
Now our little game seems to be a bit boring, enemies come and sail through your ship and you can’t shoot. We need to add collision detection and possibility to shoot the enemies to make it a bit more interesting. Let’s start with shooting! Add the following member variables to GamePage.xaml.cs:

private List<Ellipse> bullets = new List<Ellipse>(); // Bullets on the screen
private bool gameRunning = true; // Did we die already

and for ellipses we need to add also this using:

using Windows.UI.Xaml.Shapes;

Now we edit the OnFire –method we added on the second part of the tutorial. Add the following code to it:

if (gameRunning)
{
    var bullet = new Ellipse
    {
        Width = 5, Height = 5, Fill = new SolidColorBrush(Colors.Red)
    };
    bullet.Margin = new Thickness(shipPosition + (Rocket.Width/2) - (bullet.Width/2),
        shipHorizontalPosition + 2, 0, 0);
    LayoutRoot.Children.Add(bullet);
    bullets.Add(bullet);
}

The code above checks that the player is still alive, and if yes, creates a new ellipse as a bullet and adds it to the bullet list, so we can later easily check if any of the bullets hit anything on the screen. At this point we have static bullets in the screen, so logical step is to add moving functionality to the game by adding this:

private void MoveBullet(Ellipse ellipse)
{
    if ((ellipse.Margin.Top - 10) > 0)
    {
        ellipse.Margin = new Thickness(ellipse.Margin.Left, ellipse.Margin.Top - 10, 0, 0);
        HitTest(ellipse);
    }
    else
    {
        bullets.Remove(ellipse);
        LayoutRoot.Children.Remove(ellipse);
    }
}

The code above moves the bullet 10 pixels upwards until it goes off the screen. At that point it is removed from the bullet list. Let’s add the HitTest method to see if our bullets actually hit anything:

private void HitTest(Ellipse ellipse)
{
    for (int i = 0; i < enemies.Count; i++)
    {
        var enemyInFire = new Rect(enemies[i].Location.X, enemies[i].Location.Y, enemies[i].ActualWidth, enemies[i].ActualHeight);
        if (enemyInFire.Contains(new Point(ellipse.Margin.Left, ellipse.Margin.Top)))
        {
            Score += enemies[i].Worth;
            ScoreBoard.Text = Score.ToString();
            if (Score > App.Highscore)
            {
                App.Highscore = Score;
                HighscoreBoard.Text = Score.ToString();
            }
            LayoutRoot.Children.Remove(ellipse);
            bullets.Remove(ellipse);
            enemies[i].Dead = true;
            return;
        }
    }
}

To move the bullets, add this to the GameLoop -method, over the // TODO – move bullets text:

for (int i = 0; i < bullets.Count; i++)
{
    MoveBullet(bullets[i]);
}

At this point, you’re ready to shoot some aliens from outer space! But it kind of gets boring as there’s no way you can die yet, as we’re not testing if the aliens hit you. Let’s add few more things and it starts to come together. First we need the famous game over notification. Add this member variable to GamePage.xaml.cs:

private TextBlock GameOver = new TextBlock();

After that we need a crash test method:

private void CrashTest()
{
    for (int i = 0; i < enemies.Count; i++)
    {
        var enemyCreature = new Rect(enemies[i].Location.X, enemies[i].Location.Y, enemies[i].ActualWidth, enemies[i].ActualHeight);
        enemyCreature.Intersect(new Rect(Rocket.Margin.Left, Rocket.Margin.Top, Rocket.ActualWidth,
            Rocket.Margin.Top));
        if (!enemyCreature.IsEmpty)
        {
            CompositionTarget.Rendering -= GameLoop;
            Move.Completed -= MoveStars;
 
            GameOver.Text = "Game Over!";
            GameOver.FontSize = 48;
            GameOver.VerticalAlignment = VerticalAlignment.Center;
            GameOver.HorizontalAlignment = HorizontalAlignment.Center;
            Grid.SetColumn(GameOver, 1);
 
            MainGrid.Children.Add(GameOver);
            gameRunning = false;
 
            if (App.Highscore < Score)
            {
                App.Highscore = Score;
            }
        }
    }
}

Finally we need to add to the GameLoop call to the CrashTest, overwriting the // TODO – collision test with call to our method: CrashTest();

If you try the game, you soon realize that if the enemies crash to your ship, that’s the end of you! This is the end of part three of Universal Games for Windows. On the next post we’ll continue to improve the game by adding universal high score system, levels, navigation and other relevant things to finish up the game.

Download the solution so far from here

Posted in Uncategorized | Leave a comment