Creating your Own Hidden Object Game with VB.NET - Part 3 - Adding Advanced Features

Introduction

Welcome back! This is the last installment of this series. In this installment we will learn how to add a Hint Facility, how to change screen resolution, and how to make the game much more finalized. That means that we will make an introduction screen, and an ending screen, as well as a next stage. We have lots of work to do, so let us get started. We'll do it form by form.

frmJJM

The very first thing we need to do concerning the design, is to add the Hint resources and Hint Picturebox onto frmJJM. You are welcome to make use of my Hint pictures or to create your own. We will need two. One showing when a Hint hasn't been clicked, and one showing when a Hint was clicked upon. On frmJJM add a new Picturebox, and give it the following properties:

PropertySetting
Name picJJMHint
BackColor Black
Dock Left
Location 0, 0

Add a timer control onto frmJJM and give it the following properties

PropertySetting
Name tmrJJMHint
Interval 1000

Add the following code behind the tmrJJMHint_Tick event :

    Private Sub tmrJJMHint_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrJJMHint.Tick
        Static i As Integer ' Keep live counter

        If i >= 5 Then 'After 5 seconds
            picItems(RandIndex).Image = picSources(RandIndex) 'Get Picture
            picJJMHint.Image = New Bitmap(My.Resources.hint1) 'Show Hint Busy Picture
            i = 0 'Reset Counter
            tmrJJMHint.Stop()
        End If

        i += 1 'Increment Counter
    End Sub

This simply shows another Hint picture once it is clicked on. It waits five seconds then reverts back to the previous picture, cueing the user that the Hint feature is ready to be used again. To make the Hint feature work properly we will need to add the following variable :

Private intHintsUsed As Integer 'Keeps track of how many hints were used

Add the picJJMHint_Click event procedure:

    Private Sub picJJMHint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles picJJMHint.Click

        If intRemain > 0 Then 'If there are items to be found
            picJJMHint.Image = New Bitmap(My.Resources.hint) 'Change Hint picture

            GenHint() 'Generate a random number, ie. random picture to be shown as a hint
            intScore -= 40 'Penalize player for using a Hint
            intHintsUsed += 1 'Start Hints Counter

            If intScore <= 0 Then intScore = 0 'Make sure Score isn't less than 0
            picJJMInfo.Invalidate()
        End If
    End Sub

I make mention of GenHint inside the above sub. This sub we will add next :

    Private Sub GenHint()

        RandGen = New Random(Now.Millisecond) 'Generate random number
        RandIndex = RandGen.Next(0, 19) 'Limit number between 0 and 19

        VisHint(picItems(RandIndex)) 'Give visible clue of an item to player

        tmrJJMHint.Start() 'Start Hint timer

    End Sub

This sub generates a random number between 0 and 19 and then, based on that, makes use of the VisHint sub to give a hint to the player. This hint will be in the form of a dimmed copy screen of the picture in question. This makes the item quite identifiable. Let us add the VisHint sub now :

    Private Sub VisHint(ByVal SelPic As PictureBox)
        SelPic.CreateGraphics() 'Enable Drawing Capabilities

        SelPic.CreateGraphics().Clear(SelPic.BackColor) 'Clear Existing Drawings

        Dim SelGradWidth As Integer = 50 'Border is 50

        'Set Colours for gradients
        Dim SelGradCol1 As Color = Color.Transparent
        Dim SelGradCol2 As Color = Color.Fuchsia

        Dim JJMPen As New Pen(Brushes.Black, 7) 'New Pen

        'Image size 
        Dim SelPicHeight As Integer = SelPic.Image.Height
        Dim SelPicWidth As Integer = SelPic.Image.Width

        'Draw Rectangle Points
        Dim SelPicPoint1 = New Point(0, 0)
        Dim SelPicPoint2 = New Point(0, SelPicHeight + (SelGradWidth * 2))
        Dim SelPicPoint3 = New Point(SelPicWidth + (SelGradWidth * 2), (SelGradWidth * 2) + SelPicHeight)
        Dim SelPicPoint4 = New Point(SelPicWidth + (SelGradWidth * 2), 0)

        'Calculate Area
        Dim SelPicArea As New Rectangle(0, 0, (SelGradWidth * 2) + SelPicWidth, (SelGradWidth * 2) + SelPicHeight)

        Dim JJMBrush As New PathGradientBrush( _
            New Point() {SelPicPoint1, SelPicPoint2, SelPicPoint3, SelPicPoint4}) 'Normal Brush

        JJMBrush.CenterPoint = New PointF( _
            ((SelGradWidth * 2) + SelPicWidth) / 2, _
            ((SelGradWidth * 2) + SelPicHeight) / 2) 'Centered Brush

        JJMBrush.CenterColor = SelGradCol2 'Set Center Colour
        JJMBrush.SurroundColors = New Color() {SelGradCol1} 'Set Surrounding Colours

        SelPic.CreateGraphics().FillRectangle(JJMBrush, SelPicArea) 'Fill
        SelPic.CreateGraphics().DrawRectangle(JJMPen, SelPicArea) 'Draw

        ' Draw Copied Image
        SelPic.CreateGraphics().DrawImage(SelPic.Image, New PointF(SelGradWidth, SelGradWidth))
    End Sub

It looks more complicated than it is. The trick is to get the image in question's dimensions and copy that onto a new image. With a twist... I created a gradient brush and made use of its CenterColor and SurroundColors properties to layer a gradient skin onto the copied image. This gives a nice effect. The next two pictures show how the Hint button changes when it is clicked, as well as a how the hint looks, in this case how it looks on the violin item.

Shows Hint is busy processing
Figure 1 - Shows Hint is busy processing

The Hint Effect
Figure 2 - The Hint effect.

Hints used on frmSummary
Figure 3 - Hints used on frmSummary

Creating your Own Hidden Object Game with VB.NET - Part 3 - Adding Advanced Features - Page 2

frmSummary

Add a Button to frmSummary, inside of the pnlNext panel and give it the following properties :

PropertySetting
Name btnJJMContinue
Dock Left
FlatStyle System
Text Continue to Solve the Mystery

Write the next code segment under btnJJMContinue :

    Private Sub btnJJMContinue_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnJJMContinue.Click
        Dim F2 As New frmJJMStage2 'Create New frmJJMStage2 object

        F2.Show() 'Show
    End Sub

This shows a next form named frmJJMStage2, which we obviously should add. Do so now please.

frmJJMStage2

The idea behind this form is not to have a hidden object screen such as frmJJM. Usually with Hidden object games, when all of the stages have been completed, there is one final stage - as with any other game I suppose. Now, with this stage, we will place many many pictures that look the same and hide Nicholas the snake behind all of them. Who is Nicholas? Well, he is the king's pet snake that we need to find. More on that later.

What I have done here was to go to PhotoShop and create picture slices. I then load them on this form, and hide the picture of Nicholas underneath them. You are welcome to use my pictures as I am supplying them with this article in any case. Add the following familiar code to frmStage2

Public Class frmJJMStage2
    Private picItems(60) As PictureBox 'Declare enough Pictureboxes

    Private Sub frmJJMStage2_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim RandomClass As New Random() 'We will randomly place all dynamic pictureboxes

        Dim Y As Integer 'Y position
        Dim X As Integer 'X position

        For i As Integer = 1 To 58 'Create pictureboxes
            picItems(i) = New PictureBox

            AddHandler picItems(i).Click, AddressOf picItems_Click 'Assign event

            Y = RandomClass.Next(500) 'Position randomly
            X = RandomClass.Next(500)

            With picItems(i) 'Set properties
                .SizeMode = PictureBoxSizeMode.AutoSize
                .BackColor = System.Drawing.Color.Transparent
                .Visible = True
                .Image = Image.FromFile(Application.StartupPath & "\LawnSlices\lawn_" & i.ToString & ".gif") 'Load pictures
                .Location = New Point(X, Y)
            End With

            Me.picInner.Controls.Add(picItems(i))

        Next i

        picItems(59) = New PictureBox 'Nicholas the snake
        AddHandler picItems(59).Click, AddressOf Nicholas_Click 'Assign handler so that we know he has been found

        With picItems(59) 'Set properties
            .SizeMode = PictureBoxSizeMode.AutoSize
            .BackColor = System.Drawing.Color.Transparent
            .Visible = True
            .Image = Image.FromFile(Application.StartupPath & "\LawnSlices\nicholas.gif")
            .SendToBack() 'Hide it far far away, beneath most other pictureboxes
            .Location = New Point(X, Y)
        End With

        Me.picInner.Controls.Add(picItems(59)) 'Add it

    End Sub

    Private Sub picItems_Click(ByVal sender As Object, ByVal e As EventArgs)

        Dim pic As PictureBox = DirectCast(sender, PictureBox)
        pic.Visible = False 'Hide pictureboxes when clicked
    End Sub

    Private Sub Nicholas_Click(ByVal sender As Object, ByVal e As EventArgs)

        Dim pic As PictureBox = DirectCast(sender, PictureBox)

        Dim FE As New frmEnding 'Show last screen when Nicholas has been found
        FE.Show()
    End Sub

    Private Sub btnJJMExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnJJMExit.Click
        Application.Exit() 'Exit
    End Sub
End Class

We have done this before with frmJJM, so the code should be familiar. Your stage 2 screen ( if you get there! :) ) should look like the following picture, when run.

Stage 2
Figure 3 - Stage 2

Nicholas is visible now
Figure 4 - Nicholas is visible now

You will remember that I made reference to frmEnding inside frmStage2. frmEnding will appear as soon as Nicholas is found. Before I go into depth about frmEnding, we should probably start with frmIntro - which will end up being the first screen of our game. frmEnding and frmIntro looks exactly the same. Let us add both. Once added, set the following properties and add the appropriate controls to frmIntro and frmEnding.

ControlPropertySetting
Form Name frmIntro
  BackColor Black
  FormBorderStyle None
  ShowInTaskbar False
  WindowState Maximized
Panel Name pnlIntro
  BackColor Black
  Dock Bottom
Button Name btnStart
  Dock Left
  FlatStyle System
  Text Start Investigation
Button Name btnExit
  Dock Right
  FlatStyle System
  Text Exit
PictureBox Name picIntro
  Image JungleJiveMystery_Part_3.My.Resources.Resources.rewardnote ( I will supply this pic.)
  Size 700, 700
  SizeMode AutoSize
Form Name frmEnding
  BackColor Black
  FormBorderStyle None
  ShowInTaskbar False
  WindowState Maximized
Panel Name pnlEnd
  BackColor Black
  Dock Bottom
Button Name btnStart
  Dock Left
  FlatStyle System
Button Name btnExit
  Dock Right
  FlatStyle System
  Text Exit
PictureBox Name picEnd
  Dock Fill
  Image JungleJiveMystery_Part_3.My.Resources.Resources.endnote ( I will supply this pic.)
  Size 694, 660

Add the next code to frmIntro :

Public Class frmIntro

    Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click
        Application.Exit() 'Exit

    End Sub

    Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
        Dim FGame As New frmJJM 'Show Stage 1
        frmJJM.Show()
        Me.Hide()

    End Sub
End Class

And this to frmEnding :

Public Class frmEnding

    Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click
        Application.Exit()
    End Sub
End Class

All we need to do now is to set frmIntro as the Startup form for our project. Do that by clicking Project, Properties, Application tab, Startup Form. This was simple. We just added an introductory screen and an ending screen. This gives our game substance, as well as a Story line. The story goes as follows.

frmIntro displaying the beginning of our story
Figure 5 - frmIntro displaying the beginning of our story

frmEnding displaying the end of our story
Figure 6 - frmEnding displaying the end of our story

Almost done! All we need to do now is to ensure that our game displays properly on all systems. That means that we have to change the user's screen resolution at the start of the game, and reset it to what it was before the game was started. This makes the game so much more professional and final.

Creating your Own Hidden Object Game with VB.NET - Part 3 - Adding Advanced Features - Page 3

modScreenRes

Add the display changing code into modScreenRes.

Imports System.Runtime.InteropServices

Module modScreenRes

    'Retrieves information about one of the graphic modes for a display device.
    Private Declare Auto Function EnumDisplaySettings Lib "user32.dll" ( _
    <MarshalAs(UnmanagedType.LPTStr)> ByVal lpszDeviceName As String, _
    ByVal iModeNum As Int32, _
    ByRef lpDevMode As DEVMODE) As Boolean

    'Changes the settings of the default display device to the specified graphics mode
    Private Declare Auto Function ChangeDisplaySettings Lib "user32.dll" ( _
        ByRef lpDevMode As DEVMODE, _
        ByVal dwFlags As Int32) As Int32

    Private Const DM_BITSPERPEL As Int32 = &H40000 'How many bits per pixel, usually 96
    Private Const DM_PELSWIDTH As Int32 = &H80000 'Width
    Private Const DM_PELSHEIGHT As Int32 = &H100000 'Height

    Private Const DISP_CHANGE_SUCCESSFUL As Int32 = 0 'Was the change successfull

    'Contains the coordinates of a point.
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure POINTL
        Public x As Int32
        Public y As Int32
    End Structure

    'Contains information about the initialization and environment of a printer or a display device.
    <StructLayout(LayoutKind.Explicit)> _
    Private Structure DEVMODE_1
        <FieldOffset(0)> Public dmOrientation As Int16
        <FieldOffset(2)> Public dmPaperSize As Int16
        <FieldOffset(4)> Public dmPaperLength As Int16
        <FieldOffset(6)> Public dmPaperWidth As Int16
        <FieldOffset(0)> Public dmPosition As POINTL
    End Structure

    'Contains information about the initialization and environment of a printer or a display device.
    <StructLayout(LayoutKind.Explicit)> _
    Private Structure DEVMODE_2
        <FieldOffset(0)> Public dmDisplayFlags As Int32
        <FieldOffset(0)> Public dmNup As Int32
    End Structure

    'Contains information about the initialization and environment of a printer or a display device.
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Private Structure DEVMODE
        Private Const CCDEVICENAME As Int32 = 32
        Private Const CCFORMNAME As Int32 = 32

        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCDEVICENAME)> _
        Public dmDeviceName As String
        Public dmSpecVersion As Int16
        Public dmDriverVersion As Int16
        Public dmSize As Int16
        Public dmDriverExtra As Int16
        Public dmFields As Int32
        Public D1 As DEVMODE_1
        Public dmScale As Int16
        Public dmCopies As Int16
        Public dmDefaultSource As Int16
        Public dmPrintQuality As Int16
        Public dmColor As Int16
        Public dmDuplex As Int16
        Public dmYResolution As Int16
        Public dmTTOption As Int16
        Public dmCollate As Int16
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCFORMNAME)> _
        Public dmFormName As String
        Public dmUnusedPadding As Int16
        Public dmBitsPerPel As Int16
        Public dmPelsWidth As Int32
        Public dmPelsHeight As Int32
        Public D2 As DEVMODE_2
        Public dmDisplayFrequency As Int32
        Public dmICMMethod As Int32
        Public dmICMIntent As Int32
        Public dmMediaType As Int32
        Public dmDitherType As Int32
        Public dmReserved1 As Int32
        Public dmReserved2 As Int32
        Public dmPanningWidth As Int32
        Public dmPanningHeight As Int32
    End Structure

    'Change Resolution
    Public Function ChangeRes( _
        ByVal ScreenWidth As Int32, _
        ByVal ScreenHeight As Int32, _
        ByVal ScreenBitsPerPixel As Int16) As Boolean

        Dim dmDev As DEVMODE

        If Not EnumDisplaySettings(Nothing, 0, dmDev) Then 'Get display settings
            Return False
        Else

            With dmDev 'Set new properties
                .dmFields = _
                    DM_PELSWIDTH Or DM_PELSHEIGHT Or DM_BITSPERPEL
                .dmPelsWidth = ScreenWidth
                .dmPelsHeight = ScreenHeight
                .dmBitsPerPel = ScreenBitsPerPixel
            End With

            Return (ChangeDisplaySettings(dmDev, 0) = DISP_CHANGE_SUCCESSFUL) 'Do it!

        End If
    End Function
End Module

We make use of the EnumDisplaySettings API here to obtain the user's display settings and store them. Then, we make use of the ChangeDisplaySettings API along with the DEVMODE structure to change to the new display settings, and back. MSDN is a great resource if you need to familiarize yourself with APIs.

The module won't run on its own. We need to call our ChangeRes function at the start of the game, as well as when the game exits. Open frmIntro's Designer file and add the following to it.

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        ChangeRes(1024, 768, 96) '1024 x 768 Resolution + 96 Dots Per Inch
    End Sub

And finally, add the next code segment to frmJJM.

    Private Sub frmJJM_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        ChangeRes(HorSize, VertSize, 96)
    End Sub

So, at the very start of our game, we set the resolution that fits our game. Once frmJJM is unloaded (along with the other forms when we Exit), we reset the resolution to what it was initially.

Last note. I am including my pictures, as well as this project as a download for you. You will need to unzip the Images and LawnSlices into the Bin\Debug directory of your project first, before you run my samples, as this might cause exceptions.

Conclusion

As always, I hope you have enjoyed this article series as much as I did teaching it to you. It is quite difficult to come up with new ideas lately. If you have followed my articles, you will see that I am very curious about things, and try to replicate them. Maybe inspiration will come knocking again, soon! Until then, have fun! Cheers!



Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • CentreCorp is a fully integrated and diversified property management and real estate service company, specializing in the "shopping center" segment, and is one of the premier retail service providers in North America. Company executives travel a great deal, carrying a number of traveling laptops with critical current business data, and no easy way to back up to the network outside the office. Read this case study to learn how CentreCorp implemented a suite of business continuity services that included …

Most Popular Programming Stories

More for Developers

RSS Feeds