Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Speedster

0.00/5 (No votes)
13 Dec 2010 1  
The fastest three wheeled sprinter in WPF
SpeedsterScreenshot1.jpg

Introduction

I had the idea to design something in Expression Design that would spawn into a WPF project to simulate a 'super-fast', four wheeled (three in this case), sleek machine, and later WPF Speedster was born. The nice looking vehicle you see in the screenshot above functions more as a dragster and I should warn you that despite its appearance, WPF Speedster is not a game. Despite this heart breaking fact, you can still drive through/past the scenic region you see in the background on a straight road, which is suitably convenient for the Speedster.

Requirements

To run the project provided from the download link above, you can use either of the following:

  • Visual Studio 2010
  • Expression Blend

NB: If you're using the Express Editions of Visual Studio, ensure that you run the project using Visual Basic Express.

The Speedster

How It Works

To drive the Speedster, just press the right arrow key and off she goes. Take note of how fast she reaches top speed... a fine machine indeed. Unfortunately the speedster will slow to a complete halt when it runs out of fuel and your joy ride is over. Again, I'll remind you that this isn't a game so my apologies if you feel cheated.

Design and Layout

As I mentioned earlier, I designed the speedster in Expression Design. I have actually never attended a graphics design class and most of what I know in Expression Design I learned by reading the Help documentation and playing around with its tools and features. That said, I hope I did a decent job and 'ugly' does not cross your mind when you're using the app. But I digress...

The following screenshot shows the general layout of elements of concern in Expression Blend's Objects and Timeline panel:

SpeedsterScreenshot2.jpg

NB: LayoutRoot is a Canvas content control.

The StackPanel, SpeedsterStackPanel, contains several Image objects as child elements providing us with a background of substantial length. The Width property of the StackPanel is set to Auto so that its width increases as you add more images.

SpeedsterScreenshot3.jpg

The image that forms the children of SpeedsterStackPanel is visible from the project panel:

SpeedsterScreenshot4.jpg

WPFRacerRoad.png is actually a panorama photo which I edited a bit, especially by adding the road. If you want to implement your own background, I suggest you use a panorama photo 402px in height. To add WPFRacerRoad.png to the SpeedsterStackPanel, ensure that the StackPanel is the active object, right click the image in the Project panel and select Insert from the context menu.

Simulating motion here is a matter of moving SpeedsterStackPanel along its x-axis while rotating two groups of elements found in the element WPFRacer ; RWheelTreads and FWheelThings both of which are Viewboxes in which several elements are grouped.

SpeedsterScreenshot5.jpg

Two other groups of elements found in the SpeedoFueloMeter Viewbox are used to simulate acceleration, deceleration, and Speedster's fuel consumption, i.e., SpeedGaugeNeedle and FuelGaugeNeedle.

SpeedsterScreenshot6.jpgSpeedsterScreenshot7.jpg

NB: The center points of the two groups are not as shown in the above two top screenshots. It only appears as so because I selected the paths that make up the groups. Selecting the Viewboxes as shown in the last two screenshots reveals their actual center points.

The Code

For the Speedster to take off, you have to press the right arrow key so we check which key has been pressed in the MainWindow_KeyDown event and call the necessary method:

Private Sub MainWindow_KeyDown(ByVal sender As Object, _
	ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.KeyDown
    If e.Key = Key.Right Then
        Accelerator()
    End If
End Sub
 Private Sub Accelerator()
    ' Get the current x position of the StackPanel in the 
    ' LayoutRoot.
    StackPanelLeft = Canvas.GetLeft(SpeedsterStackPanel)
    
    ' If speedster is decelerating stop Timer whose
    ' Tick event handler simulates deceleration.
    If IsDecelerating = True Then
        DecelerateTimer.Stop()
    End If
    
    If CanMoveForward = True Then
        ' To create illusion of gradually increasing speed increase
        ' variable values with each continued key press.
        If Move < MaxMove Then
            Move += 0.45
        Else
            Move = MaxMove
        End If
        
        If WheelsRotation < MaxWheelsRotation Then
            WheelsRotation += 0.2
        Else
            WheelsRotation = MaxWheelsRotation
        End If
        
        ' Move StackPanel to the left to create illusion
        ' of movement.
        Canvas.SetLeft(SpeedsterStackPanel, (StackPanelLeft - Move))
        
        RearWheelRotateTr.Angle += WheelsRotation
        FrontWheelRotateTr.Angle += WheelsRotation
        
        ' Rotate FuelGaugeNeedle.
        Fuelometer()
        ' Rotate Speedometer needle.
        Speedometer()
        
    Else
        ' The speedster is now out of fuel.
        If Move > 0 Then
            ' Gradually reduce variable value to create illusion
            ' of decreasing speed.
            Move -= 0.45
            Canvas.SetLeft(SpeedsterStackPanel, (StackPanelLeft - Move))
            
            If WheelsRotation > 0 Then
                ' Gradually reduce variable value to create illusion
                ' of decreasing speed.
                WheelsRotation -= 0.2
                RearWheelRotateTr.Angle += WheelsRotation
                FrontWheelRotateTr.Angle += WheelsRotation
            End If
            
            ' Reduce angle of speedometer needle to simulate
            ' decreasing speed.
            If SpeedoNeedleRotateTr.Angle > 0 Then
                SpeedoNeedleRotateTr.Angle -= SpeedoNeedleRotation
                SpeedGaugeNeedle.RenderTransform = SpeedoNeedleRotateTr
            End If
            
        Else
            Canvas.SetLeft(SpeedsterStackPanel, StackPanelLeft)
        End If
    End If
    
    RWheelTreads.RenderTransform = RearWheelRotateTr
    FWheelThings.RenderTransform = FrontWheelRotateTr
End Sub

The Speedometer method that is called in the Accelerator method is used to rotate the Viewbox, SpeedGaugeNeedle, for obvious purposes, as the user continues to press the right arrow key:

' Rotate Speedometer needle.
Private Sub Speedometer()
    If SpeedoNeedleRotateTr.Angle < 270 Then
        SpeedoNeedleRotateTr.Angle += SpeedoNeedleRotation
        SpeedGaugeNeedle.RenderTransform = SpeedoNeedleRotateTr

        If SpeedoNeedleRotation = 0 Then
            SpeedoNeedleRotation = 2.7
        End If
    End If
End Sub

A different implementation of acceleration here would be to use a Timer (DispatcherTimer in WPF). Assuming that you want Move to reach MaxMove (45) in 5 sec, using a DispatcherTimer whose Interval is a Timespan of 100 ms, you would increment Move by a value of 0.9 during the DispatcherTimer's Tick event. In this 5 sec scenario, SpeedGaugeNeedle would hit a maximum rotation angle of 270° in the same amount of time.

To simulate fuel consumption, the method Fuelometer is called:

' Rotate FuelGauge needle.
Private Sub Fuelometer()
    If FuelNeedleRotateTr.Angle < 147 Then
        FuelNeedleRotateTr.Angle = _
		Math.Abs(Canvas.GetLeft(SpeedsterStackPanel) * FuelChange)
        FuelGaugeNeedle.RenderTransform = FuelNeedleRotateTr

        If FuelEmptyStoryboard = True Then
            If FuelNeedleRotateTr.Angle > 80 Then
                Dim OutOfFuel As Storyboard
                OutOfFuel = CType(Me.Resources("TankEmpty"), Storyboard)
                OutOfFuel.Begin(Me)
                ' Set variable to False so that storyboard will only
                ' be played once.
                FuelEmptyStoryboard = False
            End If
        End If

    Else
        CanMoveForward = False
        FuelNeedleRotateTr.Angle = 147
    End If
End Sub

This method also helps to ensure that the white regions of the window are not shown. Shifting of SpeedsterStackPanel to the left will stop way before the right edge of the StackPanel reaches the right end of the window.

When you release the right arrow key, the Speedster decelerates gradually. This is taken care of by the MainWindow_KeyUp event handler:

Private Sub MainWindow_KeyUp(ByVal sender As Object, _
	ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.KeyUp
    If e.Key = Key.Right Then
        If CanMoveForward = True Then
            Decelerator()
        End If
    End If
End Sub

The Decelerator method sets a boolean variable value indicating that the Speedster is decelerating, sets variables that are used to simulate deceleration, and calls the Start method of a Timer object:

 Private Sub Decelerator()
    Dim TimeTillStop As Double
    Dim TicksTillStop As Double
    Dim SpeedoTimeTillZero As Double
    Dim SpeedoTicksTillZero As Double
    Dim WheelsTimeTillStop As Double
    Dim WheelsTickTillStop As Double
    
    IsDecelerating = True
    ' Get and set value by which movement of StackPanel
    ' should be reduced to create an illusion of deceleration
    ' during the tick event. 
    ' [100 milliseconds = 1 Timer Tick]
    ' Speedster will stop in 6 sec if Move = MaxMove.
    TimeTillStop = (Move * 6) / MaxMove
    TicksTillStop = TimeTillStop * 10
    deceleration = Move / TicksTillStop
    
    ' Get value for reseting Speedometer needle to
    ' zero.
    SpeedoTimeTillZero = (SpeedoNeedleRotateTr.Angle * 6) / 270
    SpeedoTicksTillZero = SpeedoTimeTillZero * 10
    SpeedoNeedleToZero = SpeedoNeedleRotateTr.Angle / SpeedoTicksTillZero
    
    WheelsTimeTillStop = (WheelsRotation * 6) / MaxWheelsRotation
    WheelsTickTillStop = WheelsTimeTillStop * 10
    WheelsRotationToZero = WheelsRotation / WheelsTickTillStop
    
    DecelerateTimer.Start()
End Sub

The local variable TimeTillStop is basically how much time, in seconds, it would take to get the current value of the global variable Move to zero.

The DecelerateTimer's Tick event is handled by the method DecelerateTimer_Tick.

 Private Sub DecelerateTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
    StackPanelLeft = Canvas.GetLeft(SpeedsterStackPanel)
    
    If Move > 0 Then
        Move -= deceleration
        Canvas.SetLeft(SpeedsterStackPanel, (StackPanelLeft - Move))
    Else
        Canvas.SetLeft(SpeedsterStackPanel, StackPanelLeft)
        DecelerateTimer.Stop()
    End If
    
    If SpeedoNeedleRotateTr.Angle > 0 Then
        SpeedoNeedleRotateTr.Angle -= SpeedoNeedleToZero
        SpeedGaugeNeedle.RenderTransform = SpeedoNeedleRotateTr
    Else
        SpeedoNeedleRotateTr.Angle = 0
    End If
    
    If WheelsRotation > 0 Then
        WheelsRotation -= WheelsRotationToZero
        FrontWheelRotateTr.Angle += WheelsRotation
        RearWheelRotateTr.Angle += WheelsRotation
        
        RWheelTreads.RenderTransform = RearWheelRotateTr
        FWheelThings.RenderTransform = FrontWheelRotateTr
    End If
    
End Sub

Conclusion

I wanted the Speedster to be environmentally conscious causing zero air or noise pollution (which is a good excuse for not implementing sound effects). If you are of a different opinion or preference, feel free to make the necessary adjustments. In case you're wondering, the 'green' Speedster runs on some yet unnamed non-hydrocarbon fuel and the 'massive' exhaust does not emit any ungreen gases.

This is definitely a hobby project. I know some of you might be of the opinion that a lot more features can be added and if that is the case, go ahead and add them. I have already done the design work and a bit of coding, so if you feel like spicing up the Speedster, go right ahead.

I hope you enjoyed reading the article and driving the Speedster. Thanks!

History

  • 7th Dec, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here