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:
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.
The image that forms the children of SpeedsterStackPanel
is visible from the project panel:
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 Viewbox
es in which several elements are grouped.
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
.
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 Viewbox
es 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()
StackPanelLeft = Canvas.GetLeft(SpeedsterStackPanel)
If IsDecelerating = True Then
DecelerateTimer.Stop()
End If
If CanMoveForward = True Then
If Move < MaxMove Then
Move += 0.45
Else
Move = MaxMove
End If
If WheelsRotation < MaxWheelsRotation Then
WheelsRotation += 0.2
Else
WheelsRotation = MaxWheelsRotation
End If
Canvas.SetLeft(SpeedsterStackPanel, (StackPanelLeft - Move))
RearWheelRotateTr.Angle += WheelsRotation
FrontWheelRotateTr.Angle += WheelsRotation
Fuelometer()
Speedometer()
Else
If Move > 0 Then
Move -= 0.45
Canvas.SetLeft(SpeedsterStackPanel, (StackPanelLeft - Move))
If WheelsRotation > 0 Then
WheelsRotation -= 0.2
RearWheelRotateTr.Angle += WheelsRotation
FrontWheelRotateTr.Angle += WheelsRotation
End If
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:
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:
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)
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
TimeTillStop = (Move * 6) / MaxMove
TicksTillStop = TimeTillStop * 10
deceleration = Move / TicksTillStop
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