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 Viewboxs 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 ie. SpeedGaugeNeedle and FuelGaugeNeedle.

SpeedsterScreenshot6.jpg  SpeedsterScreenshot7.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 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 enviromentally 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 alot 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
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"